aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2019-10-07 11:18:13 -0700
committerRob Mensching <rob@firegiant.com>2019-10-07 11:59:14 -0700
commit860676fa5b40a1904478151e9b4934c004e7db63 (patch)
tree83fabd53f2a68dcf56bc8da66d88e115af3764b0
parent3b98dac62b47d590f3465985362d6e6fd100b1c0 (diff)
downloadwix-860676fa5b40a1904478151e9b4934c004e7db63.tar.gz
wix-860676fa5b40a1904478151e9b4934c004e7db63.tar.bz2
wix-860676fa5b40a1904478151e9b4934c004e7db63.zip
Implement Bundle build
-rw-r--r--appveyor.cmd4
-rw-r--r--src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs860
-rw-r--r--src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs166
-rw-r--r--src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs110
-rw-r--r--src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs64
-rw-r--r--src/WixToolset.Core.Burn/Bind/SearchFacade.cs197
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs65
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs56
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs69
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs93
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs55
-rw-r--r--src/WixToolset.Core.Burn/BundleBackend.cs28
-rw-r--r--src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs121
-rw-r--r--src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs30
-rw-r--r--src/WixToolset.Core.Burn/Bundles/BurnCommon.cs6
-rw-r--r--src/WixToolset.Core.Burn/Bundles/BurnWriter.cs10
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs312
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs171
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs399
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs59
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs134
-rw-r--r--src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs78
-rw-r--r--src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs66
-rw-r--r--src/WixToolset.Core.Burn/Bundles/PackageFacade.cs55
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs23
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs425
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs102
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs20
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs89
-rw-r--r--src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs49
-rw-r--r--src/WixToolset.Core.Burn/BurnBackendFactory.cs3
-rw-r--r--src/WixToolset.Core.Burn/VerifyInterop.cs2
-rw-r--r--src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj1
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs8
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs16
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs1
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs16
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs13
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ResolvedDirectory.cs31
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs1
-rw-r--r--src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs8
-rw-r--r--src/WixToolset.Core/Bind/FileResolver.cs8
-rw-r--r--src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs2
-rw-r--r--src/WixToolset.Core/BindContext.cs4
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs11
-rw-r--r--src/WixToolset.Core/Compiler.cs2
-rw-r--r--src/WixToolset.Core/Compiler_Bundle.cs139
-rw-r--r--src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs11
-rw-r--r--src/WixToolset.Core/ExtensibilityServices/PathResolver.cs (renamed from src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs)33
-rw-r--r--src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs15
-rw-r--r--src/WixToolset.Core/ExtensibilityServices/WindowsInstallerBackendHelper.cs32
-rw-r--r--src/WixToolset.Core/Link/WixGroupingOrdering.cs31
-rw-r--r--src/WixToolset.Core/WixToolsetServiceProvider.cs4
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs52
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exebin0 -> 463360 bytes
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl10
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs11
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msibin0 -> 32768 bytes
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj7
62 files changed, 2240 insertions, 2151 deletions
diff --git a/appveyor.cmd b/appveyor.cmd
index 75d75d99..27374ae5 100644
--- a/appveyor.cmd
+++ b/appveyor.cmd
@@ -2,13 +2,13 @@
2@pushd %~dp0 2@pushd %~dp0
3@set _P=%~dp0build\Release\publish 3@set _P=%~dp0build\Release\publish
4 4
5dotnet test -c Release src\test\WixToolsetTest.CoreIntegration
6
5dotnet pack -c Release src\WixToolset.Core 7dotnet pack -c Release src\WixToolset.Core
6dotnet pack -c Release src\WixToolset.Core.Burn 8dotnet pack -c Release src\WixToolset.Core.Burn
7dotnet pack -c Release src\WixToolset.Core.WindowsInstaller 9dotnet pack -c Release src\WixToolset.Core.WindowsInstaller
8 10
9dotnet pack -c Release src\WixToolset.Core.TestPackage 11dotnet pack -c Release src\WixToolset.Core.TestPackage
10 12
11dotnet build -c Release src\test\WixToolsetTest.CoreIntegration
12
13@popd 13@popd
14@endlocal 14@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
8 using System.Globalization; 8 using System.Globalization;
9 using System.IO; 9 using System.IO;
10 using System.Linq; 10 using System.Linq;
11 using System.Reflection;
12 using WixToolset.Core.Bind; 11 using WixToolset.Core.Bind;
12 using WixToolset.Core.Burn.Bind;
13 using WixToolset.Core.Burn.Bundles; 13 using WixToolset.Core.Burn.Bundles;
14 using WixToolset.Data; 14 using WixToolset.Data;
15 using WixToolset.Data.Bind; 15 using WixToolset.Data.Burn;
16 using WixToolset.Data.Tuples;
16 using WixToolset.Extensibility; 17 using WixToolset.Extensibility;
17 using WixToolset.Extensibility.Data; 18 using WixToolset.Extensibility.Data;
18 using WixToolset.Extensibility.Services; 19 using WixToolset.Extensibility.Services;
19 20
20 // TODO: (4.0) Refactor so that these don't need to be copied.
21 // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs
22 [Flags]
23 internal enum WixFileSearchAttributes
24 {
25 Default = 0x001,
26 MinVersionInclusive = 0x002,
27 MaxVersionInclusive = 0x004,
28 MinSizeInclusive = 0x008,
29 MaxSizeInclusive = 0x010,
30 MinDateInclusive = 0x020,
31 MaxDateInclusive = 0x040,
32 WantVersion = 0x080,
33 WantExists = 0x100,
34 IsDirectory = 0x200,
35 }
36
37 [Flags]
38 internal enum WixRegistrySearchAttributes
39 {
40 Raw = 0x01,
41 Compatible = 0x02,
42 ExpandEnvironmentVariables = 0x04,
43 WantValue = 0x08,
44 WantExists = 0x10,
45 Win64 = 0x20,
46 }
47
48 internal enum WixComponentSearchAttributes
49 {
50 KeyPath = 0x1,
51 State = 0x2,
52 WantDirectory = 0x4,
53 }
54
55 [Flags]
56 internal enum WixProductSearchAttributes
57 {
58 Version = 0x1,
59 Language = 0x2,
60 State = 0x4,
61 Assignment = 0x8,
62 UpgradeCode = 0x10,
63 }
64
65 /// <summary> 21 /// <summary>
66 /// Binds a this.bundle. 22 /// Binds a this.bundle.
67 /// </summary> 23 /// </summary>
68 internal class BindBundleCommand 24 internal class BindBundleCommand
69 { 25 {
70 public BindBundleCommand(IBindContext context) 26 public BindBundleCommand(IBindContext context, IEnumerable<IBurnBackendExtension> backedExtensions)
71 { 27 {
72 //this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); 28 this.ServiceProvider = context.ServiceProvider;
29
30 this.Messaging = context.ServiceProvider.GetService<IMessaging>();
31
32 this.BackendHelper = context.ServiceProvider.GetService<IBackendHelper>();
73 33
34 this.BurnStubPath = context.BurnStubPath;
35 this.DefaultCompressionLevel = context.DefaultCompressionLevel;
74 this.DelayedFields = context.DelayedFields; 36 this.DelayedFields = context.DelayedFields;
75 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; 37 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles;
38 this.IntermediateFolder = context.IntermediateFolder;
39 this.Output = context.IntermediateRepresentation;
40 this.OutputPath = context.OutputPath;
41 this.OutputPdbPath = context.OutputPdbPath;
42 //this.VariableResolver = context.VariableResolver;
76 43
77 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); 44 this.BackendExtensions = backedExtensions;
78
79 this.BackendExtensions = extensionManager.GetServices<IBurnBackendExtension>();
80 } 45 }
81 46
82 public CompressionLevel DefaultCompressionLevel { private get; set; } 47 private IServiceProvider ServiceProvider { get; }
48
49 private IMessaging Messaging { get; }
50
51 private IBackendHelper BackendHelper { get; }
52
53 private string BurnStubPath { get; }
54
55 private CompressionLevel? DefaultCompressionLevel { get; }
83 56
84 public IEnumerable<IDelayedField> DelayedFields { get; } 57 public IEnumerable<IDelayedField> DelayedFields { get; }
85 58
@@ -87,17 +60,15 @@ namespace WixToolset.Core.Burn
87 60
88 private IEnumerable<IBurnBackendExtension> BackendExtensions { get; } 61 private IEnumerable<IBurnBackendExtension> BackendExtensions { get; }
89 62
90 public Intermediate Output { private get; set; } 63 private Intermediate Output { get; }
91
92 public string OutputPath { private get; set; }
93 64
94 public string PdbFile { private get; set; } 65 private string OutputPath { get; }
95 66
96 //public TableDefinitionCollection TableDefinitions { private get; set; } 67 private string OutputPdbPath { get; }
97 68
98 public string IntermediateFolder { private get; set; } 69 private string IntermediateFolder { get; }
99 70
100 public IVariableResolver VariableResolver { private get; set; } 71 private IVariableResolver VariableResolver { get; }
101 72
102 public IEnumerable<IFileTransfer> FileTransfers { get; private set; } 73 public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
103 74
@@ -105,196 +76,177 @@ namespace WixToolset.Core.Burn
105 76
106 public void Execute() 77 public void Execute()
107 { 78 {
108 throw new NotImplementedException(); 79 var section = this.Output.Sections.Single();
109#if TODO 80
110 this.FileTransfers = Enumerable.Empty<FileTransfer>(); 81 var fileTransfers = new List<IFileTransfer>();
111 this.ContentFilePaths = Enumerable.Empty<string>(); 82 var trackedFiles = new List<ITrackedFile>();
112 83
113 // First look for data we expect to find... Chain, WixGroups, etc. 84 // First look for data we expect to find... Chain, WixGroups, etc.
114 85
115 // We shouldn't really get past the linker phase if there are 86 // We shouldn't really get past the linker phase if there are
116 // no group items... that means that there's no UX, no Chain, 87 // no group items... that means that there's no UX, no Chain,
117 // *and* no Containers! 88 // *and* no Containers!
118 Table chainPackageTable = this.GetRequiredTable("WixBundlePackage"); 89 var chainPackageTuples = this.GetRequiredTuples<WixBundlePackageTuple>();
119 90
120 Table wixGroupTable = this.GetRequiredTable("WixGroup"); 91 var wixGroupTuples = this.GetRequiredTuples<WixGroupTuple>();
121 92
122 // Ensure there is one and only one row in the WixBundle table. 93 // Ensure there is one and only one row in the WixBundle table.
123 // The compiler and linker behavior should have colluded to get 94 // The compiler and linker behavior should have colluded to get
124 // this behavior. 95 // this behavior.
125 WixBundleRow bundleRow = (WixBundleRow)this.GetSingleRowTable("WixBundle"); 96 var bundleTuple = this.GetSingleTuple<WixBundleTuple>();
97
98 bundleTuple.BundleId = Guid.NewGuid().ToString("B").ToUpperInvariant();
126 99
127 bundleRow.PerMachine = true; // default to per-machine but the first-per user package wil flip the bundle per-user. 100 bundleTuple.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user.
128 101
129 // Ensure there is one and only one row in the WixBootstrapperApplication table. 102 // Ensure there is one and only one row in the WixBootstrapperApplication table.
130 // The compiler and linker behavior should have colluded to get 103 // The compiler and linker behavior should have colluded to get
131 // this behavior. 104 // this behavior.
132 Row baRow = this.GetSingleRowTable("WixBootstrapperApplication"); 105 var bundleApplicationTuple = this.GetSingleTuple<WixBootstrapperApplicationTuple>();
133 106
134 // Ensure there is one and only one row in the WixChain table. 107 // Ensure there is one and only one row in the WixChain table.
135 // The compiler and linker behavior should have colluded to get 108 // The compiler and linker behavior should have colluded to get
136 // this behavior. 109 // this behavior.
137 WixChainRow chainRow = (WixChainRow)this.GetSingleRowTable("WixChain"); 110 var chainTuple = this.GetSingleTuple<WixChainTuple>();
138 111
139 if (Messaging.Instance.EncounteredError) 112 if (this.Messaging.EncounteredError)
140 { 113 {
141 return; 114 return;
142 } 115 }
143 116
144 // If there are any fields to resolve later, create the cache to populate during bind. 117 // If there are any fields to resolve later, create the cache to populate during bind.
145 IDictionary<string, string> variableCache = null; 118 var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null;
146 if (this.DelayedFields.Any())
147 {
148 variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
149 }
150 119
151 // TODO: Although the WixSearch tables are defined in the Util extension, 120 // TODO: Although the WixSearch tables are defined in the Util extension,
152 // the Bundle Binder has to know all about them. We hope to revisit all 121 // the Bundle Binder has to know all about them. We hope to revisit all
153 // of this in the 4.0 timeframe. 122 // of this in the 4.0 timeframe.
154 IEnumerable<WixSearchInfo> orderedSearches = this.OrderSearches(); 123 var orderedSearches = this.OrderSearches(section);
155 124
125#if THIS_SHOULD_BE_DELETED_SINCE_RESOLVE_DOES_THIS_NOW
156 // Extract files that come from cabinet files (this does not extract files from merge modules). 126 // Extract files that come from cabinet files (this does not extract files from merge modules).
157 { 127 {
158 var extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand(); 128 var extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand();
159 extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = ExpectedEmbeddedFiles; 129 extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = ExpectedEmbeddedFiles;
160 extractEmbeddedFilesCommand.Execute(); 130 extractEmbeddedFilesCommand.Execute();
161 } 131 }
132#endif
162 133
163 // Get the explicit payloads. 134 // Get the explicit payloads.
164 RowDictionary<WixBundlePayloadRow> payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]); 135 var payloadTuples = section.Tuples.OfType<WixBundlePayloadTuple>().ToDictionary(t => t.Id.Id);
165 136
166 // Update explicitly authored payloads with their parent package and container (as appropriate) 137 // Update explicitly authored payloads with their parent package and container (as appropriate)
167 // to make it easier to gather the payloads later. 138 // to make it easier to gather the payloads later.
168 foreach (WixGroupRow row in wixGroupTable.RowsAs<WixGroupRow>()) 139 foreach (var groupTuple in wixGroupTuples)
169 { 140 {
170 if (ComplexReferenceChildType.Payload == row.ChildType) 141 if (ComplexReferenceChildType.Payload == groupTuple.ChildType)
171 { 142 {
172 WixBundlePayloadRow payload = payloads.Get(row.ChildId); 143 var payloadTuple = payloadTuples[groupTuple.ChildId];
173 144
174 if (ComplexReferenceParentType.Package == row.ParentType) 145 if (ComplexReferenceParentType.Package == groupTuple.ParentType)
175 { 146 {
176 Debug.Assert(String.IsNullOrEmpty(payload.Package)); 147 Debug.Assert(String.IsNullOrEmpty(payloadTuple.PackageRef));
177 payload.Package = row.ParentId; 148 payloadTuple.PackageRef = groupTuple.ParentId;
178 } 149 }
179 else if (ComplexReferenceParentType.Container == row.ParentType) 150 else if (ComplexReferenceParentType.Container == groupTuple.ParentType)
180 { 151 {
181 Debug.Assert(String.IsNullOrEmpty(payload.Container)); 152 Debug.Assert(String.IsNullOrEmpty(payloadTuple.ContainerRef));
182 payload.Container = row.ParentId; 153 payloadTuple.ContainerRef = groupTuple.ParentId;
183 } 154 }
184 else if (ComplexReferenceParentType.Layout == row.ParentType) 155 else if (ComplexReferenceParentType.Layout == groupTuple.ParentType)
185 { 156 {
186 payload.LayoutOnly = true; 157 payloadTuple.LayoutOnly = true;
187 } 158 }
188 } 159 }
189 } 160 }
190 161
191 List<FileTransfer> fileTransfers = new List<FileTransfer>(); 162 var layoutDirectory = Path.GetDirectoryName(this.OutputPath);
192 string layoutDirectory = Path.GetDirectoryName(this.OutputPath);
193 163
194 // Process the explicitly authored payloads. 164 // Process the explicitly authored payloads.
195 ISet<string> processedPayloads; 165 ISet<string> processedPayloads;
196 { 166 {
197 ProcessPayloadsCommand command = new ProcessPayloadsCommand(); 167 var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, payloadTuples.Values, bundleTuple.DefaultPackagingType, layoutDirectory);
198 command.Payloads = payloads.Values;
199 command.DefaultPackaging = bundleRow.DefaultPackagingType;
200 command.LayoutDirectory = layoutDirectory;
201 command.Execute(); 168 command.Execute();
202 169
203 fileTransfers.AddRange(command.FileTransfers); 170 fileTransfers.AddRange(command.FileTransfers);
204 171
205 processedPayloads = new HashSet<string>(payloads.Keys); 172 processedPayloads = new HashSet<string>(payloadTuples.Keys);
206 } 173 }
207 174
208 IDictionary<string, PackageFacade> facades; 175 IDictionary<string, PackageFacade> facades;
209 { 176 {
210 GetPackageFacadesCommand command = new GetPackageFacadesCommand(); 177 var command = new GetPackageFacadesCommand(chainPackageTuples, section);
211 command.PackageTable = chainPackageTable;
212 command.ExePackageTable = this.Output.Tables["WixBundleExePackage"];
213 command.MsiPackageTable = this.Output.Tables["WixBundleMsiPackage"];
214 command.MspPackageTable = this.Output.Tables["WixBundleMspPackage"];
215 command.MsuPackageTable = this.Output.Tables["WixBundleMsuPackage"];
216 command.Execute(); 178 command.Execute();
217 179
218 facades = command.PackageFacades; 180 facades = command.PackageFacades;
219 } 181 }
220 182
221 // Process each package facade. Note this is likely to add payloads and other rows to tables so 183 // Process each package facade. Note this is likely to add payloads and other tuples so
222 // note that any indexes created above may be out of date now. 184 // note that any indexes created above may be out of date now.
223 foreach (PackageFacade facade in facades.Values) 185 foreach (var facade in facades.Values)
224 { 186 {
225 switch (facade.Package.Type) 187 switch (facade.PackageTuple.Type)
226 { 188 {
227 case WixBundlePackageType.Exe: 189 case WixBundlePackageType.Exe:
228 { 190 {
229 ProcessExePackageCommand command = new ProcessExePackageCommand(); 191 var command = new ProcessExePackageCommand(facade, payloadTuples);
230 command.AuthoredPayloads = payloads; 192 command.Execute();
231 command.Facade = facade; 193 }
232 command.Execute(); 194 break;
233
234 // ? variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.ExePackage.Manufacturer);
235 }
236 break;
237 195
238 case WixBundlePackageType.Msi: 196 case WixBundlePackageType.Msi:
239 { 197 {
240 var command = new ProcessMsiPackageCommand(); 198 var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, payloadTuples);
241 command.AuthoredPayloads = payloads; 199 command.Execute();
242 command.Facade = facade;
243 command.BackendExtensions = this.BackendExtensions;
244 command.MsiFeatureTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiFeature"]);
245 command.MsiPropertyTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiProperty"]);
246 command.PayloadTable = this.Output.Tables["WixBundlePayload"];
247 command.RelatedPackageTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleRelatedPackage"]);
248 command.Execute();
249
250 if (null != variableCache)
251 {
252 variableCache.Add(String.Concat("packageLanguage.", facade.Package.WixChainItemId), facade.MsiPackage.ProductLanguage.ToString());
253
254 if (null != facade.MsiPackage.Manufacturer)
255 {
256 variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.MsiPackage.Manufacturer);
257 }
258 }
259 200
260 } 201 if (null != variableCache)
261 break; 202 {
203 var msiPackage = (WixBundleMsiPackageTuple)facade.SpecificPackageTuple;
204 variableCache.Add(String.Concat("packageLanguage.", facade.PackageId), msiPackage.ProductLanguage.ToString());
262 205
263 case WixBundlePackageType.Msp: 206 if (null != msiPackage.Manufacturer)
264 { 207 {
265 ProcessMspPackageCommand command = new ProcessMspPackageCommand(); 208 variableCache.Add(String.Concat("packageManufacturer.", facade.PackageId), msiPackage.Manufacturer);
266 command.AuthoredPayloads = payloads;
267 command.Facade = facade;
268 command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]);
269 command.Execute();
270 } 209 }
271 break; 210 }
272 211
273 case WixBundlePackageType.Msu: 212 }
274 { 213 break;
275 ProcessMsuPackageCommand command = new ProcessMsuPackageCommand(); 214
276 command.Facade = facade; 215 case WixBundlePackageType.Msp:
277 command.Execute(); 216 {
278 } 217 var command = new ProcessMspPackageCommand(this.Messaging, section, facade, payloadTuples);
279 break; 218 command.Execute();
219 }
220 break;
221
222 case WixBundlePackageType.Msu:
223 {
224 var command = new ProcessMsuPackageCommand(facade, payloadTuples);
225 command.Execute();
226 }
227 break;
280 } 228 }
281 229
282 if (null != variableCache) 230 if (null != variableCache)
283 { 231 {
284 BindBundleCommand.PopulatePackageVariableCache(facade.Package, variableCache); 232 BindBundleCommand.PopulatePackageVariableCache(facade.PackageTuple, variableCache);
285 } 233 }
286 } 234 }
287 235
236 if (this.Messaging.EncounteredError)
237 {
238 return;
239 }
240
288 // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) 241 // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later)
289 // are present. 242 // are present.
290 payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]); 243 payloadTuples = section.Tuples.OfType<WixBundlePayloadTuple>().ToDictionary(t => t.Id.Id);
291 244
292 // Process the payloads that were added by processing the packages. 245 // Process the payloads that were added by processing the packages.
293 { 246 {
294 ProcessPayloadsCommand command = new ProcessPayloadsCommand(); 247 var toProcess = payloadTuples.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList();
295 command.Payloads = payloads.Values.Where(r => !processedPayloads.Contains(r.Id)).ToList(); 248
296 command.DefaultPackaging = bundleRow.DefaultPackagingType; 249 var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, toProcess, bundleTuple.DefaultPackagingType, layoutDirectory);
297 command.LayoutDirectory = layoutDirectory;
298 command.Execute(); 250 command.Execute();
299 251
300 fileTransfers.AddRange(command.FileTransfers); 252 fileTransfers.AddRange(command.FileTransfers);
@@ -303,45 +255,43 @@ namespace WixToolset.Core.Burn
303 } 255 }
304 256
305 // Set the package metadata from the payloads now that we have the complete payload information. 257 // Set the package metadata from the payloads now that we have the complete payload information.
306 ILookup<string, WixBundlePayloadRow> payloadsByPackage = payloads.Values.ToLookup(p => p.Package);
307
308 { 258 {
309 foreach (PackageFacade facade in facades.Values) 259 var payloadsByPackageId = payloadTuples.Values.ToLookup(p => p.PackageRef);
260
261 foreach (var facade in facades.Values)
310 { 262 {
311 facade.Package.Size = 0; 263 facade.PackageTuple.Size = 0;
312 264
313 IEnumerable<WixBundlePayloadRow> packagePayloads = payloadsByPackage[facade.Package.WixChainItemId]; 265 var packagePayloads = payloadsByPackageId[facade.PackageId];
314 266
315 foreach (WixBundlePayloadRow payload in packagePayloads) 267 foreach (var payload in packagePayloads)
316 { 268 {
317 facade.Package.Size += payload.FileSize; 269 facade.PackageTuple.Size += payload.FileSize;
318 } 270 }
319 271
320 if (!facade.Package.InstallSize.HasValue) 272 if (!facade.PackageTuple.InstallSize.HasValue)
321 { 273 {
322 facade.Package.InstallSize = facade.Package.Size; 274 facade.PackageTuple.InstallSize = facade.PackageTuple.Size;
323
324 } 275 }
325 276
326 WixBundlePayloadRow packagePayload = payloads[facade.Package.PackagePayload]; 277 var packagePayload = payloadTuples[facade.PackageTuple.PayloadRef];
327 278
328 if (String.IsNullOrEmpty(facade.Package.Description)) 279 if (String.IsNullOrEmpty(facade.PackageTuple.Description))
329 { 280 {
330 facade.Package.Description = packagePayload.Description; 281 facade.PackageTuple.Description = packagePayload.Description;
331 } 282 }
332 283
333 if (String.IsNullOrEmpty(facade.Package.DisplayName)) 284 if (String.IsNullOrEmpty(facade.PackageTuple.DisplayName))
334 { 285 {
335 facade.Package.DisplayName = packagePayload.DisplayName; 286 facade.PackageTuple.DisplayName = packagePayload.DisplayName;
336 } 287 }
337 } 288 }
338 } 289 }
339 290
340
341 // Give the UX payloads their embedded IDs... 291 // Give the UX payloads their embedded IDs...
342 int uxPayloadIndex = 0; 292 var uxPayloadIndex = 0;
343 { 293 {
344 foreach (WixBundlePayloadRow payload in payloads.Values.Where(p => Compiler.BurnUXContainerId == p.Container)) 294 foreach (var payload in payloadTuples.Values.Where(p => BurnConstants.BurnUXContainerName == p.ContainerRef))
345 { 295 {
346 // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even 296 // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even
347 // downloaded. The current engine requires the UX to be fully present before any downloading starts, 297 // downloaded. The current engine requires the UX to be fully present before any downloading starts,
@@ -349,7 +299,7 @@ namespace WixToolset.Core.Burn
349 // into the temporary UX directory correctly, so we don't allow external either. 299 // into the temporary UX directory correctly, so we don't allow external either.
350 if (PackagingType.Embedded != payload.Packaging) 300 if (PackagingType.Embedded != payload.Packaging)
351 { 301 {
352 Messaging.Instance.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName)); 302 this.Messaging.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.SourceFile.Path));
353 payload.Packaging = PackagingType.Embedded; 303 payload.Packaging = PackagingType.Embedded;
354 } 304 }
355 305
@@ -360,12 +310,12 @@ namespace WixToolset.Core.Burn
360 if (0 == uxPayloadIndex) 310 if (0 == uxPayloadIndex)
361 { 311 {
362 // If we didn't get any UX payloads, it's an error! 312 // If we didn't get any UX payloads, it's an error!
363 throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication")); 313 throw new WixException(ErrorMessages.MissingBundleInformation("BootstrapperApplication"));
364 } 314 }
365 315
366 // Give the embedded payloads without an embedded id yet an embedded id. 316 // Give the embedded payloads without an embedded id yet an embedded id.
367 int payloadIndex = 0; 317 var payloadIndex = 0;
368 foreach (WixBundlePayloadRow payload in payloads.Values) 318 foreach (var payload in payloadTuples.Values)
369 { 319 {
370 Debug.Assert(PackagingType.Unknown != payload.Packaging); 320 Debug.Assert(PackagingType.Unknown != payload.Packaging);
371 321
@@ -377,38 +327,38 @@ namespace WixToolset.Core.Burn
377 } 327 }
378 } 328 }
379 329
330 if (this.Messaging.EncounteredError)
331 {
332 return;
333 }
334
380 // Determine patches to automatically slipstream. 335 // Determine patches to automatically slipstream.
381 { 336 {
382 AutomaticallySlipstreamPatchesCommand command = new AutomaticallySlipstreamPatchesCommand(); 337 var command = new AutomaticallySlipstreamPatchesCommand(section, facades.Values);
383 command.PackageFacades = facades.Values;
384 command.SlipstreamMspTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleSlipstreamMsp"]);
385 command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]);
386 command.Execute(); 338 command.Execute();
387 } 339 }
388 340
389 // If catalog files exist, non-embedded payloads should validate with the catalogs. 341 // If catalog files exist, non-embedded payloads should validate with the catalogs.
390 IEnumerable<WixBundleCatalogRow> catalogs = this.Output.Tables["WixBundleCatalog"].RowsAs<WixBundleCatalogRow>(); 342 var catalogs = section.Tuples.OfType<WixBundleCatalogTuple>().ToList();
391 343
392 if (catalogs.Any()) 344 if (catalogs.Count > 0)
393 { 345 {
394 VerifyPayloadsWithCatalogCommand command = new VerifyPayloadsWithCatalogCommand(); 346 var command = new VerifyPayloadsWithCatalogCommand(this.Messaging, catalogs, payloadTuples.Values);
395 command.Catalogs = catalogs;
396 command.Payloads = payloads.Values;
397 command.Execute(); 347 command.Execute();
398 } 348 }
399 349
400 if (Messaging.Instance.EncounteredError) 350 if (this.Messaging.EncounteredError)
401 { 351 {
402 return; 352 return;
403 } 353 }
404 354
405 IEnumerable<PackageFacade> orderedFacades; 355 IEnumerable<PackageFacade> orderedFacades;
406 IEnumerable<WixBundleRollbackBoundaryRow> boundaries; 356 IEnumerable<WixBundleRollbackBoundaryTuple> boundaries;
407 { 357 {
408 OrderPackagesAndRollbackBoundariesCommand command = new OrderPackagesAndRollbackBoundariesCommand(); 358 var groupTuples = section.Tuples.OfType<WixGroupTuple>();
409 command.Boundaries = new RowDictionary<WixBundleRollbackBoundaryRow>(this.Output.Tables["WixBundleRollbackBoundary"]); 359 var boundaryTuplesById = section.Tuples.OfType<WixBundleRollbackBoundaryTuple>().ToDictionary(b => b.Id.Id);
410 command.PackageFacades = facades; 360
411 command.WixGroupTable = wixGroupTable; 361 var command = new OrderPackagesAndRollbackBoundariesCommand(this.Messaging, groupTuples, boundaryTuplesById, facades);
412 command.Execute(); 362 command.Execute();
413 363
414 orderedFacades = command.OrderedPackageFacades; 364 orderedFacades = command.OrderedPackageFacades;
@@ -418,280 +368,122 @@ namespace WixToolset.Core.Burn
418 // Resolve any delayed fields before generating the manifest. 368 // Resolve any delayed fields before generating the manifest.
419 if (this.DelayedFields.Any()) 369 if (this.DelayedFields.Any())
420 { 370 {
421 var resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand(); 371 var resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand(this.Messaging, this.DelayedFields, variableCache);
422 resolveDelayedFieldsCommand.OutputType = this.Output.Type;
423 resolveDelayedFieldsCommand.DelayedFields = this.DelayedFields;
424 resolveDelayedFieldsCommand.ModularizationGuid = null;
425 resolveDelayedFieldsCommand.VariableCache = variableCache;
426 resolveDelayedFieldsCommand.Execute(); 372 resolveDelayedFieldsCommand.Execute();
427 } 373 }
428 374
429 // Set the overridable bundle provider key. 375 Dictionary<string, ProvidesDependencyTuple> dependencyTuplesByKey;
430 this.SetBundleProviderKey(this.Output, bundleRow); 376 {
377 var command = new ProcessDependencyProvidersCommand(this.Messaging, section, facades);
378 command.Execute();
431 379
432 // Import or generate dependency providers for packages in the manifest. 380 bundleTuple.ProviderKey = command.BundleProviderKey; // set the overridable bundle provider key.
433 this.ProcessDependencyProviders(this.Output, facades); 381 dependencyTuplesByKey = command.DependencyTuplesByKey;
382 }
434 383
435 // Update the bundle per-machine/per-user scope based on the chained packages. 384 // Update the bundle per-machine/per-user scope based on the chained packages.
436 this.ResolveBundleInstallScope(bundleRow, orderedFacades); 385 this.ResolveBundleInstallScope(section, bundleTuple, orderedFacades);
437 386
438 // Generate the core-defined BA manifest tables... 387 // Generate the core-defined BA manifest tables...
439 { 388 {
440 CreateBootstrapperApplicationManifestCommand command = new CreateBootstrapperApplicationManifestCommand(); 389 var command = new CreateBootstrapperApplicationManifestCommand(section, bundleTuple, orderedFacades, uxPayloadIndex, payloadTuples, this.IntermediateFolder);
441 command.BundleRow = bundleRow;
442 command.ChainPackages = orderedFacades;
443 command.LastUXPayloadIndex = uxPayloadIndex;
444 command.MsiFeatures = this.Output.Tables["WixBundleMsiFeature"].RowsAs<WixBundleMsiFeatureRow>();
445 command.Output = this.Output;
446 command.Payloads = payloads;
447 command.TableDefinitions = this.TableDefinitions;
448 command.TempFilesLocation = this.IntermediateFolder;
449 command.Execute(); 390 command.Execute();
450 391
451 WixBundlePayloadRow baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; 392 var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow;
452 payloads.Add(baManifestPayload); 393 payloadTuples.Add(baManifestPayload.Id.Id, baManifestPayload);
453 } 394 }
454 395
455 //foreach (BinderExtension extension in this.Extensions) 396#if TODO
456 //{ 397 foreach (BinderExtension extension in this.Extensions)
457 // extension.PostBind(this.Context); 398 {
458 //} 399 extension.PostBind(this.Context);
400 }
401#endif
459 402
460 // Create all the containers except the UX container first so the manifest (that goes in the UX container) 403 // Create all the containers except the UX container first so the manifest (that goes in the UX container)
461 // can contain all size and hash information about the non-UX containers. 404 // can contain all size and hash information about the non-UX containers.
462 RowDictionary<WixBundleContainerRow> containers = new RowDictionary<WixBundleContainerRow>(this.Output.Tables["WixBundleContainer"]); 405 WixBundleContainerTuple uxContainer;
463 406 IEnumerable<WixBundlePayloadTuple> uxPayloads;
464 ILookup<string, WixBundlePayloadRow> payloadsByContainer = payloads.Values.ToLookup(p => p.Container); 407 IEnumerable<WixBundleContainerTuple> containers;
465
466 int attachedContainerIndex = 1; // count starts at one because UX container is "0".
467
468 IEnumerable<WixBundlePayloadRow> uxContainerPayloads = Enumerable.Empty<WixBundlePayloadRow>();
469
470 foreach (WixBundleContainerRow container in containers.Values)
471 { 408 {
472 IEnumerable<WixBundlePayloadRow> containerPayloads = payloadsByContainer[container.Id]; 409 var command = new CreateNonUXContainers(this.BackendHelper, section, bundleApplicationTuple, payloadTuples, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel);
473 410 command.Execute();
474 if (!containerPayloads.Any())
475 {
476 if (container.Id != Compiler.BurnDefaultAttachedContainerId)
477 {
478 // TODO: display warning that we're ignoring container that ended up with no paylods in it.
479 }
480 }
481 else if (Compiler.BurnUXContainerId == container.Id)
482 {
483 container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name);
484 container.AttachedContainerIndex = 0;
485
486 // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first
487 // in the list since that is the Payload that Burn attempts to load.
488 List<WixBundlePayloadRow> uxPayloads = new List<WixBundlePayloadRow>();
489
490 string baPayloadId = baRow.FieldAsString(0);
491
492 foreach (WixBundlePayloadRow uxPayload in containerPayloads)
493 {
494 if (uxPayload.Id == baPayloadId)
495 {
496 uxPayloads.Insert(0, uxPayload);
497 }
498 else
499 {
500 uxPayloads.Add(uxPayload);
501 }
502 }
503 411
504 uxContainerPayloads = uxPayloads; 412 fileTransfers.AddRange(command.FileTransfers);
505 }
506 else
507 {
508 container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name);
509 413
510 // Add detached containers to the list of file transfers. 414 uxContainer = command.UXContainer;
511 if (ContainerType.Detached == container.Type) 415 uxPayloads = command.UXContainerPayloads;
512 { 416 containers = command.Containers;
513 FileTransfer transfer; 417 }
514 if (FileTransfer.TryCreate(container.WorkingPath, Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer)) 418
515 { 419 // Create the bundle manifest.
516 transfer.Built = true; 420 string manifestPath;
517 fileTransfers.Add(transfer); 421 {
518 } 422 var executableName = Path.GetFileName(this.OutputPath);
519 }
520 else // update the attached container index.
521 {
522 Debug.Assert(ContainerType.Attached == container.Type);
523 423
524 container.AttachedContainerIndex = attachedContainerIndex; 424 var command = new CreateBurnManifestCommand(this.Messaging, this.BackendExtensions, executableName, section, bundleTuple, containers, chainTuple, orderedFacades, boundaries, uxPayloads, payloadTuples, orderedSearches, catalogs, this.IntermediateFolder);
525 ++attachedContainerIndex; 425 command.Execute();
526 }
527 426
528 this.CreateContainer(container, containerPayloads, null); 427 manifestPath = command.OutputPath;
529 }
530 } 428 }
531 429
532 // Create the bundle manifest then UX container. 430 // Create the UX container.
533 string manifestPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml");
534 { 431 {
535 var command = new CreateBurnManifestCommand(); 432 var command = new CreateContainerCommand(manifestPath, uxPayloads, uxContainer.WorkingPath, this.DefaultCompressionLevel);
536 command.BackendExtensions = this.BackendExtensions;
537 command.Output = this.Output;
538
539 command.BundleInfo = bundleRow;
540 command.Chain = chainRow;
541 command.Containers = containers;
542 command.Catalogs = catalogs;
543 command.ExecutableName = Path.GetFileName(this.OutputPath);
544 command.OrderedPackages = orderedFacades;
545 command.OutputPath = manifestPath;
546 command.RollbackBoundaries = boundaries;
547 command.OrderedSearches = orderedSearches;
548 command.Payloads = payloads;
549 command.UXContainerPayloads = uxContainerPayloads;
550 command.Execute(); 433 command.Execute();
551 }
552
553 WixBundleContainerRow uxContainer = containers[Compiler.BurnUXContainerId];
554 this.CreateContainer(uxContainer, uxContainerPayloads, manifestPath);
555
556 // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note
557 // that today, the x64 Burn uses the x86 stub.
558 string stubPlatform = (Platform.X64 == bundleRow.Platform) ? "x86" : bundleRow.Platform.ToString();
559
560 string stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe");
561 string bundleTempPath = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath));
562
563 Messaging.Instance.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile));
564 434
565 string bundleFilename = Path.GetFileName(this.OutputPath); 435 uxContainer.Hash = command.Hash;
566 if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) 436 uxContainer.Size = command.Size;
567 {
568 Messaging.Instance.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename));
569 } 437 }
570 438
571 FileTransfer bundleTransfer;
572 if (FileTransfer.TryCreate(bundleTempPath, this.OutputPath, true, "Bundle", bundleRow.SourceLineNumbers, out bundleTransfer))
573 { 439 {
574 bundleTransfer.Built = true; 440 var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleTuple, uxContainer, containers, this.BurnStubPath);
575 fileTransfers.Add(bundleTransfer); 441 command.Execute();
576 }
577 442
578 File.Copy(stubFile, bundleTempPath, true); 443 fileTransfers.Add(command.Transfer);
579 File.SetAttributes(bundleTempPath, FileAttributes.Normal); 444 }
580 445
581 this.UpdateBurnResources(bundleTempPath, this.OutputPath, bundleRow); 446#if TODO
447 this.Pdb = new Pdb { Output = output };
582 448
583 // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers 449 if (!String.IsNullOrEmpty(this.OutputPdbPath))
584 // if they should be attached.
585 using (BurnWriter writer = BurnWriter.Open(bundleTempPath))
586 { 450 {
587 FileInfo burnStubFile = new FileInfo(bundleTempPath); 451 var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final);
588 writer.InitializeBundleSectionData(burnStubFile.Length, bundleRow.BundleId); 452 trackedFiles.Add(trackPdb);
589
590 // Always attach the UX container first
591 writer.AppendContainer(uxContainer.WorkingPath, BurnWriter.Container.UX);
592 453
593 // Now append all other attached containers 454 this.Pdb.Save(trackPdb.Path);
594 foreach (WixBundleContainerRow container in containers.Values)
595 {
596 if (ContainerType.Attached == container.Type)
597 {
598 // The container was only created if it had payloads.
599 if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id)
600 {
601 writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached);
602 }
603 }
604 }
605 }
606
607 if (null != this.PdbFile)
608 {
609 Pdb pdb = new Pdb();
610 pdb.Output = Output;
611 pdb.Save(this.PdbFile);
612 } 455 }
456#endif
613 457
458#if TODO // does this need to come back, or do they only need to be in TrackedFiles?
459 this.ContentFilePaths = payloadTuples.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList();
460#endif
614 this.FileTransfers = fileTransfers; 461 this.FileTransfers = fileTransfers;
615 this.ContentFilePaths = payloads.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); 462 this.TrackedFiles = trackedFiles;
616 }
617
618 private Table GetRequiredTable(string tableName)
619 {
620 Table table = this.Output.Tables[tableName];
621 if (null == table || 0 == table.Rows.Count)
622 {
623 throw new WixException(WixErrors.MissingBundleInformation(tableName));
624 }
625
626 return table;
627 }
628
629 private Row GetSingleRowTable(string tableName)
630 {
631 Table table = this.Output.Tables[tableName];
632 if (null == table || 1 != table.Rows.Count)
633 {
634 throw new WixException(WixErrors.MissingBundleInformation(tableName));
635 }
636 463
637 return table.Rows[0]; 464 // TODO: Eventually this gets removed
465 var intermediate = new Intermediate(this.Output.Id, new[] { section }, this.Output.Localizations.ToDictionary(l => l.Culture, StringComparer.OrdinalIgnoreCase), this.Output.EmbedFilePaths);
466 var trackIntermediate = this.BackendHelper.TrackFile(Path.Combine(this.IntermediateFolder, Path.GetFileName(Path.ChangeExtension(this.OutputPath, "wir"))), TrackedFileType.Intermediate);
467 intermediate.Save(trackIntermediate.Path);
468 trackedFiles.Add(trackIntermediate);
638 } 469 }
639 470
640 private List<WixSearchInfo> OrderSearches() 471 private IEnumerable<SearchFacade> OrderSearches(IntermediateSection section)
641 { 472 {
642 Dictionary<string, WixSearchInfo> allSearches = new Dictionary<string, WixSearchInfo>(); 473 var searchesById = section.Tuples
643 Table wixFileSearchTable = this.Output.Tables["WixFileSearch"]; 474 .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch ||
644 if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count) 475 t.Definition.Type == TupleDefinitionType.WixFileSearch ||
645 { 476 t.Definition.Type == TupleDefinitionType.WixProductSearch ||
646 foreach (Row row in wixFileSearchTable.Rows) 477 t.Definition.Type == TupleDefinitionType.WixRegistrySearch)
647 { 478 .ToDictionary(t => t.Id.Id);
648 WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row);
649 allSearches.Add(fileSearchInfo.Id, fileSearchInfo);
650 }
651 }
652
653 Table wixRegistrySearchTable = this.Output.Tables["WixRegistrySearch"];
654 if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count)
655 {
656 foreach (Row row in wixRegistrySearchTable.Rows)
657 {
658 WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row);
659 allSearches.Add(registrySearchInfo.Id, registrySearchInfo);
660 }
661 }
662
663 Table wixComponentSearchTable = this.Output.Tables["WixComponentSearch"];
664 if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count)
665 {
666 foreach (Row row in wixComponentSearchTable.Rows)
667 {
668 WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row);
669 allSearches.Add(componentSearchInfo.Id, componentSearchInfo);
670 }
671 }
672 479
673 Table wixProductSearchTable = this.Output.Tables["WixProductSearch"]; 480 var orderedSearches = new List<SearchFacade>(searchesById.Keys.Count);
674 if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count)
675 {
676 foreach (Row row in wixProductSearchTable.Rows)
677 {
678 WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row);
679 allSearches.Add(productSearchInfo.Id, productSearchInfo);
680 }
681 }
682 481
683 // Merge in the variable/condition info and get the canonical ordering for 482 foreach (var searchTuple in section.Tuples.OfType<WixSearchTuple>())
684 // the searches.
685 List<WixSearchInfo> orderedSearches = new List<WixSearchInfo>();
686 Table wixSearchTable = this.Output.Tables["WixSearch"];
687 if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count)
688 { 483 {
689 orderedSearches.Capacity = wixSearchTable.Rows.Count; 484 if (searchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple))
690 foreach (Row row in wixSearchTable.Rows)
691 { 485 {
692 WixSearchInfo searchInfo = allSearches[(string)row[0]]; 486 orderedSearches.Add(new SearchFacade(searchTuple, specificSearchTuple));
693 searchInfo.AddWixSearchRowInfo(row);
694 orderedSearches.Add(searchInfo);
695 } 487 }
696 } 488 }
697 489
@@ -703,9 +495,9 @@ namespace WixToolset.Core.Burn
703 /// </summary> 495 /// </summary>
704 /// <param name="package">The package with properties to cache.</param> 496 /// <param name="package">The package with properties to cache.</param>
705 /// <param name="variableCache">The property cache.</param> 497 /// <param name="variableCache">The property cache.</param>
706 private static void PopulatePackageVariableCache(WixBundlePackageRow package, IDictionary<string, string> variableCache) 498 private static void PopulatePackageVariableCache(WixBundlePackageTuple package, IDictionary<string, string> variableCache)
707 { 499 {
708 string id = package.WixChainItemId; 500 var id = package.Id.Id;
709 501
710 variableCache.Add(String.Concat("packageDescription.", id), package.Description); 502 variableCache.Add(String.Concat("packageDescription.", id), package.Description);
711 //variableCache.Add(String.Concat("packageLanguage.", id), package.Language); 503 //variableCache.Add(String.Concat("packageLanguage.", id), package.Language);
@@ -714,231 +506,63 @@ namespace WixToolset.Core.Burn
714 variableCache.Add(String.Concat("packageVersion.", id), package.Version); 506 variableCache.Add(String.Concat("packageVersion.", id), package.Version);
715 } 507 }
716 508
717 private void CreateContainer(WixBundleContainerRow container, IEnumerable<WixBundlePayloadRow> containerPayloads, string manifestFile) 509 private void ResolveBundleInstallScope(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<PackageFacade> facades)
718 { 510 {
719 CreateContainerCommand command = new CreateContainerCommand(); 511 var dependencyTuplesById = section.Tuples.OfType<ProvidesDependencyTuple>().ToDictionary(t => t.Id.Id);
720 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
721 command.Payloads = containerPayloads;
722 command.ManifestFile = manifestFile;
723 command.OutputPath = container.WorkingPath;
724 command.Execute();
725
726 container.Hash = command.Hash;
727 container.Size = command.Size;
728 }
729 512
730 private void ResolveBundleInstallScope(WixBundleRow bundleInfo, IEnumerable<PackageFacade> facades) 513 foreach (var facade in facades)
731 {
732 foreach (PackageFacade facade in facades)
733 { 514 {
734 if (bundleInfo.PerMachine && YesNoDefaultType.No == facade.Package.PerMachine) 515 if (bundleTuple.PerMachine && YesNoDefaultType.No == facade.PackageTuple.PerMachine)
735 { 516 {
736 Messaging.Instance.OnMessage(WixVerboses.SwitchingToPerUserPackage(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); 517 this.Messaging.Write(VerboseMessages.SwitchingToPerUserPackage(facade.PackageTuple.SourceLineNumbers, facade.PackageId));
737 518
738 bundleInfo.PerMachine = false; 519 bundleTuple.Attributes &= ~WixBundleAttributes.PerMachine;
739 break; 520 break;
740 } 521 }
741 } 522 }
742 523
743 foreach (PackageFacade facade in facades) 524 foreach (var facade in facades)
744 { 525 {
745 // Update package scope from bundle scope if default. 526 // Update package scope from bundle scope if default.
746 if (YesNoDefaultType.Default == facade.Package.PerMachine) 527 if (YesNoDefaultType.Default == facade.PackageTuple.PerMachine)
747 { 528 {
748 facade.Package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; 529 facade.PackageTuple.PerMachine = bundleTuple.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No;
749 } 530 }
750 531
751 // We will only register packages in the same scope as the bundle. Warn if any packages with providers 532 // We will only register packages in the same scope as the bundle. Warn if any packages with providers
752 // are in a different scope and not permanent (permanents typically don't need a ref-count). 533 // are in a different scope and not permanent (permanents typically don't need a ref-count).
753 if (!bundleInfo.PerMachine && YesNoDefaultType.Yes == facade.Package.PerMachine && !facade.Package.Permanent && 0 < facade.Provides.Count) 534 if (!bundleTuple.PerMachine &&
535 YesNoDefaultType.Yes == facade.PackageTuple.PerMachine &&
536 !facade.PackageTuple.Permanent &&
537 dependencyTuplesById.ContainsKey(facade.PackageId))
754 { 538 {
755 Messaging.Instance.OnMessage(WixWarnings.NoPerMachineDependencies(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); 539 this.Messaging.Write(WarningMessages.NoPerMachineDependencies(facade.PackageTuple.SourceLineNumbers, facade.PackageId));
756 } 540 }
757 } 541 }
758 } 542 }
759 543
760 private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleRow bundleInfo) 544 private IEnumerable<T> GetRequiredTuples<T>() where T : IntermediateTuple
761 { 545 {
762 WixToolset.Dtf.Resources.ResourceCollection resources = new WixToolset.Dtf.Resources.ResourceCollection(); 546 var tuples = this.Output.Sections.Single().Tuples.OfType<T>().ToList();
763 WixToolset.Dtf.Resources.VersionResource version = new WixToolset.Dtf.Resources.VersionResource("#1", 1033);
764 547
765 version.Load(bundleTempPath); 548 if (0 == tuples.Count)
766 resources.Add(version);
767
768 // Ensure the bundle info provides a full four part version.
769 Version fourPartVersion = new Version(bundleInfo.Version);
770 int major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major;
771 int minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor;
772 int build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build;
773 int revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision;
774
775 if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision)
776 {
777 throw new WixException(WixErrors.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version));
778 }
779
780 fourPartVersion = new Version(major, minor, build, revision);
781 version.FileVersion = fourPartVersion;
782 version.ProductVersion = fourPartVersion;
783
784 WixToolset.Dtf.Resources.VersionStringTable strings = version[1033];
785 strings["LegalCopyright"] = bundleInfo.Copyright;
786 strings["OriginalFilename"] = Path.GetFileName(outputPath);
787 strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
788 strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
789
790 if (!String.IsNullOrEmpty(bundleInfo.Name))
791 { 549 {
792 strings["ProductName"] = bundleInfo.Name; 550 throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T)));
793 strings["FileDescription"] = bundleInfo.Name;
794 } 551 }
795 552
796 if (!String.IsNullOrEmpty(bundleInfo.Publisher)) 553 return tuples;
797 {
798 strings["CompanyName"] = bundleInfo.Publisher;
799 }
800 else
801 {
802 strings["CompanyName"] = String.Empty;
803 }
804
805 if (!String.IsNullOrEmpty(bundleInfo.IconPath))
806 {
807 Dtf.Resources.GroupIconResource iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033);
808 iconGroup.ReadFromFile(bundleInfo.IconPath);
809 resources.Add(iconGroup);
810
811 foreach (Dtf.Resources.Resource icon in iconGroup.Icons)
812 {
813 resources.Add(icon);
814 }
815 }
816
817 if (!String.IsNullOrEmpty(bundleInfo.SplashScreenBitmapPath))
818 {
819 Dtf.Resources.BitmapResource bitmap = new Dtf.Resources.BitmapResource("#1", 1033);
820 bitmap.ReadFromFile(bundleInfo.SplashScreenBitmapPath);
821 resources.Add(bitmap);
822 }
823
824 resources.Save(bundleTempPath);
825 } 554 }
826 555
827//#region DependencyExtension 556 private T GetSingleTuple<T>() where T : IntermediateTuple
828 /// <summary>
829 /// Imports authored dependency providers for each package in the manifest,
830 /// and generates dependency providers for certain package types that do not
831 /// have a provider defined.
832 /// </summary>
833 /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param>
834 /// <param name="facades">An indexed collection of chained packages.</param>
835 private void ProcessDependencyProviders(Output bundle, IDictionary<string, PackageFacade> facades)
836 { 557 {
837 // First import any authored dependencies. These may merge with imported provides from MSI packages. 558 var tuples = this.Output.Sections.Single().Tuples.OfType<T>().ToList();
838 Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"];
839 if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count)
840 {
841 // Add package information for each dependency provider authored into the manifest.
842 foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows)
843 {
844 string packageId = (string)wixDependencyProviderRow[1];
845
846 PackageFacade facade = null;
847 if (facades.TryGetValue(packageId, out facade))
848 {
849 ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow);
850
851 if (String.IsNullOrEmpty(dependency.Key))
852 {
853 switch (facade.Package.Type)
854 {
855 // The WixDependencyExtension allows an empty Key for MSIs and MSPs.
856 case WixBundlePackageType.Msi:
857 dependency.Key = facade.MsiPackage.ProductCode;
858 break;
859 case WixBundlePackageType.Msp:
860 dependency.Key = facade.MspPackage.PatchCode;
861 break;
862 }
863 }
864
865 if (String.IsNullOrEmpty(dependency.Version))
866 {
867 dependency.Version = facade.Package.Version;
868 }
869
870 // If the version is still missing, a version could not be harvested from the package and was not authored.
871 if (String.IsNullOrEmpty(dependency.Version))
872 {
873 Messaging.Instance.OnMessage(WixErrors.MissingDependencyVersion(facade.Package.WixChainItemId));
874 }
875
876 if (String.IsNullOrEmpty(dependency.DisplayName))
877 {
878 dependency.DisplayName = facade.Package.DisplayName;
879 }
880 559
881 if (!facade.Provides.Merge(dependency)) 560 if (1 != tuples.Count)
882 {
883 Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId));
884 }
885 }
886 }
887 }
888
889 // Generate providers for MSI packages that still do not have providers.
890 foreach (PackageFacade facade in facades.Values)
891 { 561 {
892 string key = null; 562 throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T)));
893
894 if (WixBundlePackageType.Msi == facade.Package.Type && 0 == facade.Provides.Count)
895 {
896 key = facade.MsiPackage.ProductCode;
897 }
898 else if (WixBundlePackageType.Msp == facade.Package.Type && 0 == facade.Provides.Count)
899 {
900 key = facade.MspPackage.PatchCode;
901 }
902
903 if (!String.IsNullOrEmpty(key))
904 {
905 ProvidesDependency dependency = new ProvidesDependency(key, facade.Package.Version, facade.Package.DisplayName, 0);
906
907 if (!facade.Provides.Merge(dependency))
908 {
909 Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId));
910 }
911 }
912 } 563 }
913 }
914 564
915 /// <summary> 565 return tuples[0];
916 /// Sets the provider key for the bundle.
917 /// </summary>
918 /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param>
919 /// <param name="bundleInfo">The <see cref="BundleInfo"/> containing the provider key and other information for the bundle.</param>
920 private void SetBundleProviderKey(Output bundle, WixBundleRow bundleInfo)
921 {
922 // From DependencyCommon.cs in the WixDependencyExtension.
923 const int ProvidesAttributesBundle = 0x10000;
924
925 Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"];
926 if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count)
927 {
928 // Search the WixDependencyProvider table for the single bundle provider key.
929 foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows)
930 {
931 object attributes = wixDependencyProviderRow[5];
932 if (null != attributes && 0 != (ProvidesAttributesBundle & (int)attributes))
933 {
934 bundleInfo.ProviderKey = (string)wixDependencyProviderRow[2];
935 break;
936 }
937 }
938 }
939
940 // Defaults to the bundle ID as the provider key.
941#endif
942 } 566 }
943 } 567 }
944} 568}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Core.Burn.Bundles;
10 using WixToolset.Extensibility.Services;
11 using WixToolset.Data.Tuples;
12
13 internal class ProcessDependencyProvidersCommand
14 {
15 public ProcessDependencyProvidersCommand(IMessaging messaging, IntermediateSection section, IDictionary<string, PackageFacade> facades)
16 {
17 this.Messaging = messaging;
18 this.Section = section;
19
20 this.Facades = facades;
21 }
22
23 public string BundleProviderKey { get; private set; }
24
25 public Dictionary<string, ProvidesDependencyTuple> DependencyTuplesByKey { get; private set; }
26
27 private IMessaging Messaging { get; }
28
29 private IntermediateSection Section { get; }
30
31 private IDictionary<string, PackageFacade> Facades { get; }
32
33 /// <summary>
34 /// Sets the explicitly provided bundle provider key, if provided. And...
35 /// Imports authored dependency providers for each package in the manifest,
36 /// and generates dependency providers for certain package types that do not
37 /// have a provider defined.
38 /// </summary>
39 public void Execute()
40 {
41 var wixDependencyProviderTuples = this.Section.Tuples.OfType<WixDependencyProviderTuple>();
42
43 foreach (var wixDependencyProviderTuple in wixDependencyProviderTuples)
44 {
45 // Sets the provider key for the bundle, if it is not set already.
46 if (String.IsNullOrEmpty(this.BundleProviderKey))
47 {
48 if (wixDependencyProviderTuple.Bundle)
49 {
50 this.BundleProviderKey = wixDependencyProviderTuple.ProviderKey;
51 }
52 }
53
54 // Import any authored dependencies. These may merge with imported provides from MSI packages.
55 var packageId = wixDependencyProviderTuple.Id.Id;
56
57 if (this.Facades.TryGetValue(packageId, out var facade))
58 {
59 var dependency = new ProvidesDependencyTuple(wixDependencyProviderTuple.SourceLineNumbers, wixDependencyProviderTuple.Id)
60 {
61 PackageRef = packageId,
62 Key = wixDependencyProviderTuple.ProviderKey,
63 Version = wixDependencyProviderTuple.Version,
64 DisplayName = wixDependencyProviderTuple.DisplayName,
65 Attributes = (int)wixDependencyProviderTuple.Attributes
66 };
67
68 if (String.IsNullOrEmpty(dependency.Key))
69 {
70 switch (facade.SpecificPackageTuple)
71 {
72 // The WixDependencyExtension allows an empty Key for MSIs and MSPs.
73 case WixBundleMsiPackageTuple msiPackage:
74 dependency.Key = msiPackage.ProductCode;
75 break;
76 case WixBundleMspPackageTuple mspPackage:
77 dependency.Key = mspPackage.PatchCode;
78 break;
79 }
80 }
81
82 if (String.IsNullOrEmpty(dependency.Version))
83 {
84 dependency.Version = facade.PackageTuple.Version;
85 }
86
87 // If the version is still missing, a version could not be harvested from the package and was not authored.
88 if (String.IsNullOrEmpty(dependency.Version))
89 {
90 this.Messaging.Write(ErrorMessages.MissingDependencyVersion(facade.PackageId));
91 }
92
93 if (String.IsNullOrEmpty(dependency.DisplayName))
94 {
95 dependency.DisplayName = facade.PackageTuple.DisplayName;
96 }
97
98 this.Section.Tuples.Add(dependency);
99 }
100 }
101
102 this.DependencyTuplesByKey = this.GetDependencyTuplesByKey();
103
104 // Generate providers for MSI and MSP packages that still do not have providers.
105 foreach (var facade in this.Facades.Values)
106 {
107 string key = null;
108
109 //if (WixBundlePackageType.Msi == facade.PackageTuple.Type)
110 if (facade.SpecificPackageTuple is WixBundleMsiPackageTuple msiPackage)
111 {
112 //var msiPackage = (WixBundleMsiPackageTuple)facade.SpecificPackageTuple;
113 key = msiPackage.ProductCode;
114 }
115 //else if (WixBundlePackageType.Msp == facade.PackageTuple.Type)
116 else if (facade.SpecificPackageTuple is WixBundleMspPackageTuple mspPackage)
117 {
118 //var mspPackage = (WixBundleMspPackageTuple)facade.SpecificPackageTuple;
119 key = mspPackage.PatchCode;
120 }
121
122 if (!String.IsNullOrEmpty(key) && !this.DependencyTuplesByKey.ContainsKey(key))
123 {
124 var dependency = new ProvidesDependencyTuple(facade.PackageTuple.SourceLineNumbers, facade.PackageTuple.Id)
125 {
126 PackageRef = facade.PackageId,
127 Key = key,
128 Version = facade.PackageTuple.Version,
129 DisplayName = facade.PackageTuple.DisplayName
130 };
131
132 this.Section.Tuples.Add(dependency);
133
134 this.DependencyTuplesByKey.Add(dependency.Key, dependency);
135 }
136 }
137 }
138
139 private Dictionary<string, ProvidesDependencyTuple> GetDependencyTuplesByKey()
140 {
141 var dependencyTuplesByKey = new Dictionary<string, ProvidesDependencyTuple>();
142
143 var dependencyTuples = this.Section.Tuples.OfType<ProvidesDependencyTuple>();
144
145 foreach (var dependency in dependencyTuples)
146 {
147 if (dependencyTuplesByKey.TryGetValue(dependency.Key, out var collision))
148 {
149 // If not a perfect dependency collision, display an error.
150 if (dependency.Key != collision.Key ||
151 dependency.Version != collision.Version ||
152 dependency.DisplayName != collision.DisplayName)
153 {
154 this.Messaging.Write(ErrorMessages.DuplicateProviderDependencyKey(dependency.Key, dependency.PackageRef));
155 }
156 }
157 else
158 {
159 dependencyTuplesByKey.Add(dependency.Key, dependency);
160 }
161 }
162
163 return dependencyTuplesByKey;
164 }
165 }
166}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Represents an authored or imported dependency provider.
11 /// </summary>
12 internal sealed class ProvidesDependency
13 {
14#if TODO
15 /// <summary>
16 /// Creates a new instance of the <see cref="ProviderDependency"/> class from a <see cref="Row"/>.
17 /// </summary>
18 /// <param name="row">The <see cref="Row"/> from which data is imported.</param>
19 internal ProvidesDependency(Row row)
20 : this((string)row[2], (string)row[3], (string)row[4], (int?)row[5])
21 {
22 }
23#endif
24
25 /// <summary>
26 /// Creates a new instance of the <see cref="ProviderDependency"/> class.
27 /// </summary>
28 /// <param name="key">The unique key of the dependency.</param>
29 /// <param name="attributes">Additional attributes for the dependency.</param>
30 internal ProvidesDependency(string key, string version, string displayName, int? attributes)
31 {
32 this.Key = key;
33 this.Version = version;
34 this.DisplayName = displayName;
35 this.Attributes = attributes;
36 }
37
38 /// <summary>
39 /// Gets or sets the unique key of the package provider.
40 /// </summary>
41 internal string Key { get; set; }
42
43 /// <summary>
44 /// Gets or sets the version of the package provider.
45 /// </summary>
46 internal string Version { get; set; }
47
48 /// <summary>
49 /// Gets or sets the display name of the package provider.
50 /// </summary>
51 internal string DisplayName { get; set; }
52
53 /// <summary>
54 /// Gets or sets the attributes for the dependency.
55 /// </summary>
56 internal int? Attributes { get; set; }
57
58 /// <summary>
59 /// Gets or sets whether the dependency was imported from the package.
60 /// </summary>
61 internal bool Imported { get; set; }
62
63 /// <summary>
64 /// Gets whether certain properties are the same.
65 /// </summary>
66 /// <param name="other">Another <see cref="ProvidesDependency"/> to compare.</param>
67 /// <remarks>This is not the same as object equality, but only checks a subset of properties
68 /// to determine if the objects are similar and could be merged into a collection.</remarks>
69 /// <returns>True if certain properties are the same.</returns>
70 internal bool Equals(ProvidesDependency other)
71 {
72 if (null != other)
73 {
74 return this.Key == other.Key &&
75 this.Version == other.Version &&
76 this.DisplayName == other.DisplayName;
77 }
78
79 return false;
80 }
81
82 /// <summary>
83 /// Writes the dependency to the bundle XML manifest.
84 /// </summary>
85 /// <param name="writer">The <see cref="XmlTextWriter"/> for the bundle XML manifest.</param>
86 internal void WriteXml(XmlTextWriter writer)
87 {
88 writer.WriteStartElement("Provides");
89 writer.WriteAttributeString("Key", this.Key);
90
91 if (!String.IsNullOrEmpty(this.Version))
92 {
93 writer.WriteAttributeString("Version", this.Version);
94 }
95
96 if (!String.IsNullOrEmpty(this.DisplayName))
97 {
98 writer.WriteAttributeString("DisplayName", this.DisplayName);
99 }
100
101 if (this.Imported)
102 {
103 // The package dependency was explicitly authored into the manifest.
104 writer.WriteAttributeString("Imported", "yes");
105 }
106
107 writer.WriteEndElement();
108 }
109 }
110}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Collections.ObjectModel;
7
8 /// <summary>
9 /// A case-insensitive collection of unique <see cref="ProvidesDependency"/> objects.
10 /// </summary>
11 internal sealed class ProvidesDependencyCollection : KeyedCollection<string, ProvidesDependency>
12 {
13 /// <summary>
14 /// Creates a case-insensitive collection of unique <see cref="ProvidesDependency"/> objects.
15 /// </summary>
16 internal ProvidesDependencyCollection()
17 : base(StringComparer.InvariantCultureIgnoreCase)
18 {
19 }
20
21 /// <summary>
22 /// Adds the <see cref="ProvidesDependency"/> to the collection if it doesn't already exist.
23 /// </summary>
24 /// <param name="dependency">The <see cref="ProvidesDependency"/> to add to the collection.</param>
25 /// <returns>True if the <see cref="ProvidesDependency"/> was added to the collection; otherwise, false.</returns>
26 /// <exception cref="ArgumentNullException">The <paramref name="dependency"/> parameter is null.</exception>
27 internal bool Merge(ProvidesDependency dependency)
28 {
29 if (null == dependency)
30 {
31 throw new ArgumentNullException("dependency");
32 }
33
34 // If the dependency key is already in the collection, verify equality for a subset of properties.
35 if (this.Contains(dependency.Key))
36 {
37 ProvidesDependency current = this[dependency.Key];
38 if (!current.Equals(dependency))
39 {
40 return false;
41 }
42 }
43
44 base.Add(dependency);
45 return true;
46 }
47
48 /// <summary>
49 /// Gets the <see cref="ProvidesDependency.Key"/> for the <paramref name="dependency"/>.
50 /// </summary>
51 /// <param name="dependency">The dependency to index.</param>
52 /// <exception cref="ArgumentNullException">The <paramref name="dependency"/> parameter is null.</exception>
53 /// <returns>The <see cref="ProvidesDependency.Key"/> for the <paramref name="dependency"/>.</returns>
54 protected override string GetKeyForItem(ProvidesDependency dependency)
55 {
56 if (null == dependency)
57 {
58 throw new ArgumentNullException("dependency");
59 }
60
61 return dependency.Key;
62 }
63 }
64}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8 using WixToolset.Data.Tuples;
9
10 internal class SearchFacade
11 {
12 public SearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple)
13 {
14 this.SearchTuple = searchTuple;
15 this.SearchSpecificTuple = searchSpecificTuple;
16 }
17
18 public WixSearchTuple SearchTuple { get; }
19
20 public IntermediateTuple SearchSpecificTuple { get; }
21
22 /// <summary>
23 /// Generates Burn manifest and ParameterInfo-style markup a search.
24 /// </summary>
25 /// <param name="writer"></param>
26 public void WriteXml(XmlTextWriter writer)
27 {
28 switch (this.SearchSpecificTuple)
29 {
30 case WixComponentSearchTuple tuple:
31 this.WriteComponentSearchXml(writer, tuple);
32 break;
33 case WixFileSearchTuple tuple:
34 this.WriteFileSearchXml(writer, tuple);
35 break;
36 case WixProductSearchTuple tuple:
37 this.WriteProductSearchXml(writer, tuple);
38 break;
39 case WixRegistrySearchTuple tuple:
40 this.WriteRegistrySearchXml(writer, tuple);
41 break;
42 }
43 }
44
45 private void WriteCommonAttributes(XmlTextWriter writer)
46 {
47 writer.WriteAttributeString("Id", this.SearchTuple.Id.Id);
48 writer.WriteAttributeString("Variable", this.SearchTuple.Variable);
49 if (!String.IsNullOrEmpty(this.SearchTuple.Condition))
50 {
51 writer.WriteAttributeString("Condition", this.SearchTuple.Condition);
52 }
53 }
54
55 private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchTuple searchTuple)
56 {
57 writer.WriteStartElement("MsiComponentSearch");
58
59 this.WriteCommonAttributes(writer);
60
61 writer.WriteAttributeString("ComponentId", searchTuple.Guid);
62
63 if (!String.IsNullOrEmpty(searchTuple.ProductCode))
64 {
65 writer.WriteAttributeString("ProductCode", searchTuple.ProductCode);
66 }
67
68 if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.KeyPath))
69 {
70 writer.WriteAttributeString("Type", "keyPath");
71 }
72 else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.State))
73 {
74 writer.WriteAttributeString("Type", "state");
75 }
76 else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.WantDirectory))
77 {
78 writer.WriteAttributeString("Type", "directory");
79 }
80
81 writer.WriteEndElement();
82 }
83
84 private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchTuple searchTuple)
85 {
86 writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch");
87
88 this.WriteCommonAttributes(writer);
89
90 writer.WriteAttributeString("Path", searchTuple.Path);
91 if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists))
92 {
93 writer.WriteAttributeString("Type", "exists");
94 }
95 else if (WixFileSearchAttributes.WantVersion == (searchTuple.Attributes & WixFileSearchAttributes.WantVersion))
96 {
97 // Can never get here for DirectorySearch.
98 writer.WriteAttributeString("Type", "version");
99 }
100 else
101 {
102 writer.WriteAttributeString("Type", "path");
103 }
104 writer.WriteEndElement();
105 }
106
107 private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchTuple tuple)
108 {
109 writer.WriteStartElement("MsiProductSearch");
110
111 this.WriteCommonAttributes(writer);
112
113 if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode))
114 {
115 writer.WriteAttributeString("UpgradeCode", tuple.Guid);
116 }
117 else
118 {
119 writer.WriteAttributeString("ProductCode", tuple.Guid);
120 }
121
122 if (0 != (tuple.Attributes & WixProductSearchAttributes.Version))
123 {
124 writer.WriteAttributeString("Type", "version");
125 }
126 else if (0 != (tuple.Attributes & WixProductSearchAttributes.Language))
127 {
128 writer.WriteAttributeString("Type", "language");
129 }
130 else if (0 != (tuple.Attributes & WixProductSearchAttributes.State))
131 {
132 writer.WriteAttributeString("Type", "state");
133 }
134 else if (0 != (tuple.Attributes & WixProductSearchAttributes.Assignment))
135 {
136 writer.WriteAttributeString("Type", "assignment");
137 }
138
139 writer.WriteEndElement();
140 }
141
142 private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchTuple tuple)
143 {
144 writer.WriteStartElement("RegistrySearch");
145
146 this.WriteCommonAttributes(writer);
147
148 switch (tuple.Root)
149 {
150 case RegistryRootType.ClassesRoot:
151 writer.WriteAttributeString("Root", "HKCR");
152 break;
153 case RegistryRootType.CurrentUser:
154 writer.WriteAttributeString("Root", "HKCU");
155 break;
156 case RegistryRootType.LocalMachine:
157 writer.WriteAttributeString("Root", "HKLM");
158 break;
159 case RegistryRootType.Users:
160 writer.WriteAttributeString("Root", "HKU");
161 break;
162 }
163
164 writer.WriteAttributeString("Key", tuple.Key);
165
166 if (!String.IsNullOrEmpty(tuple.Value))
167 {
168 writer.WriteAttributeString("Value", tuple.Value);
169 }
170
171 var existenceOnly = 0 != (tuple.Attributes & WixRegistrySearchAttributes.WantExists);
172
173 writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value");
174
175 if (0 != (tuple.Attributes & WixRegistrySearchAttributes.Win64))
176 {
177 writer.WriteAttributeString("Win64", "yes");
178 }
179
180 if (!existenceOnly)
181 {
182 if (0 != (tuple.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables))
183 {
184 writer.WriteAttributeString("ExpandEnvironment", "yes");
185 }
186
187 // We *always* say this is VariableType="string". If we end up
188 // needing to be more specific, we will have to expand the "Format"
189 // attribute to allow "number" and "version".
190
191 writer.WriteAttributeString("VariableType", "string");
192 }
193
194 writer.WriteEndElement();
195 }
196 }
197}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Utility class for all WixComponentSearches.
11 /// </summary>
12 internal class WixComponentSearchInfo : WixSearchInfo
13 {
14#if TODO
15 public WixComponentSearchInfo(Row row)
16 : this((string)row[0], (string)row[1], (string)row[2], (int)row[3])
17 {
18 }
19#endif
20
21 public WixComponentSearchInfo(string id, string guid, string productCode, int attributes)
22 : base(id)
23 {
24 this.Guid = guid;
25 this.ProductCode = productCode;
26 this.Attributes = (WixComponentSearchAttributes)attributes;
27 }
28
29 public string Guid { get; private set; }
30 public string ProductCode { get; private set; }
31 public WixComponentSearchAttributes Attributes { get; private set; }
32
33 /// <summary>
34 /// Generates Burn manifest and ParameterInfo-style markup for a component search.
35 /// </summary>
36 /// <param name="writer"></param>
37 public override void WriteXml(XmlTextWriter writer)
38 {
39 writer.WriteStartElement("MsiComponentSearch");
40 this.WriteWixSearchAttributes(writer);
41
42 writer.WriteAttributeString("ComponentId", this.Guid);
43
44 if (!String.IsNullOrEmpty(this.ProductCode))
45 {
46 writer.WriteAttributeString("ProductCode", this.ProductCode);
47 }
48
49 if (0 != (this.Attributes & WixComponentSearchAttributes.KeyPath))
50 {
51 writer.WriteAttributeString("Type", "keyPath");
52 }
53 else if (0 != (this.Attributes & WixComponentSearchAttributes.State))
54 {
55 writer.WriteAttributeString("Type", "state");
56 }
57 else if (0 != (this.Attributes & WixComponentSearchAttributes.WantDirectory))
58 {
59 writer.WriteAttributeString("Type", "directory");
60 }
61
62 writer.WriteEndElement();
63 }
64 }
65}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Utility class for all WixFileSearches (file and directory searches).
11 /// </summary>
12 internal class WixFileSearchInfo : WixSearchInfo
13 {
14#if TODO
15 public WixFileSearchInfo(Row row)
16 : this((string)row[0], (string)row[1], (int)row[9])
17 {
18 }
19#endif
20
21 public WixFileSearchInfo(string id, string path, int attributes)
22 : base(id)
23 {
24 this.Path = path;
25 this.Attributes = (WixFileSearchAttributes)attributes;
26 }
27
28 public string Path { get; private set; }
29 public WixFileSearchAttributes Attributes { get; private set; }
30
31 /// <summary>
32 /// Generates Burn manifest and ParameterInfo-style markup for a file/directory search.
33 /// </summary>
34 /// <param name="writer"></param>
35 public override void WriteXml(XmlTextWriter writer)
36 {
37 writer.WriteStartElement((0 == (this.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch");
38 this.WriteWixSearchAttributes(writer);
39 writer.WriteAttributeString("Path", this.Path);
40 if (WixFileSearchAttributes.WantExists == (this.Attributes & WixFileSearchAttributes.WantExists))
41 {
42 writer.WriteAttributeString("Type", "exists");
43 }
44 else if (WixFileSearchAttributes.WantVersion == (this.Attributes & WixFileSearchAttributes.WantVersion))
45 {
46 // Can never get here for DirectorySearch.
47 writer.WriteAttributeString("Type", "version");
48 }
49 else
50 {
51 writer.WriteAttributeString("Type", "path");
52 }
53 writer.WriteEndElement();
54 }
55 }
56}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Utility class for all WixProductSearches.
11 /// </summary>
12 internal class WixProductSearchInfo : WixSearchInfo
13 {
14#if TODO
15 public WixProductSearchInfo(Row row)
16 : this((string)row[0], (string)row[1], (int)row[2])
17 {
18 }
19#endif
20
21 public WixProductSearchInfo(string id, string guid, int attributes)
22 : base(id)
23 {
24 this.Guid = guid;
25 this.Attributes = (WixProductSearchAttributes)attributes;
26 }
27
28 public string Guid { get; private set; }
29 public WixProductSearchAttributes Attributes { get; private set; }
30
31 /// <summary>
32 /// Generates Burn manifest and ParameterInfo-style markup for a product search.
33 /// </summary>
34 /// <param name="writer"></param>
35 public override void WriteXml(XmlTextWriter writer)
36 {
37 writer.WriteStartElement("MsiProductSearch");
38 this.WriteWixSearchAttributes(writer);
39
40 if (0 != (this.Attributes & WixProductSearchAttributes.UpgradeCode))
41 {
42 writer.WriteAttributeString("UpgradeCode", this.Guid);
43 }
44 else
45 {
46 writer.WriteAttributeString("ProductCode", this.Guid);
47 }
48
49 if (0 != (this.Attributes & WixProductSearchAttributes.Version))
50 {
51 writer.WriteAttributeString("Type", "version");
52 }
53 else if (0 != (this.Attributes & WixProductSearchAttributes.Language))
54 {
55 writer.WriteAttributeString("Type", "language");
56 }
57 else if (0 != (this.Attributes & WixProductSearchAttributes.State))
58 {
59 writer.WriteAttributeString("Type", "state");
60 }
61 else if (0 != (this.Attributes & WixProductSearchAttributes.Assignment))
62 {
63 writer.WriteAttributeString("Type", "assignment");
64 }
65
66 writer.WriteEndElement();
67 }
68 }
69}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data.WindowsInstaller;
8
9 /// <summary>
10 /// Utility class for all WixRegistrySearches.
11 /// </summary>
12 internal class WixRegistrySearchInfo : WixSearchInfo
13 {
14#if TODO
15 public WixRegistrySearchInfo(Row row)
16 : this((string)row[0], (int)row[1], (string)row[2], (string)row[3], (int)row[4])
17 {
18 }
19#endif
20
21 public WixRegistrySearchInfo(string id, int root, string key, string value, int attributes)
22 : base(id)
23 {
24 this.Root = root;
25 this.Key = key;
26 this.Value = value;
27 this.Attributes = (WixRegistrySearchAttributes)attributes;
28 }
29
30 public int Root { get; private set; }
31 public string Key { get; private set; }
32 public string Value { get; private set; }
33 public WixRegistrySearchAttributes Attributes { get; private set; }
34
35 /// <summary>
36 /// Generates Burn manifest and ParameterInfo-style markup for a registry search.
37 /// </summary>
38 /// <param name="writer"></param>
39 public override void WriteXml(XmlTextWriter writer)
40 {
41 writer.WriteStartElement("RegistrySearch");
42 this.WriteWixSearchAttributes(writer);
43
44 switch (this.Root)
45 {
46 case WindowsInstallerConstants.MsidbRegistryRootClassesRoot:
47 writer.WriteAttributeString("Root", "HKCR");
48 break;
49 case WindowsInstallerConstants.MsidbRegistryRootCurrentUser:
50 writer.WriteAttributeString("Root", "HKCU");
51 break;
52 case WindowsInstallerConstants.MsidbRegistryRootLocalMachine:
53 writer.WriteAttributeString("Root", "HKLM");
54 break;
55 case WindowsInstallerConstants.MsidbRegistryRootUsers:
56 writer.WriteAttributeString("Root", "HKU");
57 break;
58 }
59
60 writer.WriteAttributeString("Key", this.Key);
61
62 if (!String.IsNullOrEmpty(this.Value))
63 {
64 writer.WriteAttributeString("Value", this.Value);
65 }
66
67 bool existenceOnly = 0 != (this.Attributes & WixRegistrySearchAttributes.WantExists);
68
69 writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value");
70
71 if (0 != (this.Attributes & WixRegistrySearchAttributes.Win64))
72 {
73 writer.WriteAttributeString("Win64", "yes");
74 }
75
76 if (!existenceOnly)
77 {
78 if (0 != (this.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables))
79 {
80 writer.WriteAttributeString("ExpandEnvironment", "yes");
81 }
82
83 // We *always* say this is VariableType="string". If we end up
84 // needing to be more specific, we will have to expand the "Format"
85 // attribute to allow "number" and "version".
86
87 writer.WriteAttributeString("VariableType", "string");
88 }
89
90 writer.WriteEndElement();
91 }
92 }
93}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Diagnostics;
7 using System.Xml;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// Utility base class for all WixSearches.
12 /// </summary>
13 internal abstract class WixSearchInfo
14 {
15 public WixSearchInfo(string id)
16 {
17 this.Id = id;
18 }
19
20#if TODO
21 public void AddWixSearchRowInfo(Row row)
22 {
23 Debug.Assert((string)row[0] == Id);
24 Variable = (string)row[1];
25 Condition = (string)row[2];
26 }
27#endif
28
29 public string Id { get; private set; }
30 public string Variable { get; private set; }
31 public string Condition { get; private set; }
32
33 /// <summary>
34 /// Generates Burn manifest and ParameterInfo-style markup a search.
35 /// </summary>
36 /// <param name="writer"></param>
37 public virtual void WriteXml(XmlTextWriter writer)
38 {
39 }
40
41 /// <summary>
42 /// Writes attributes common to all WixSearch elements.
43 /// </summary>
44 /// <param name="writer"></param>
45 protected void WriteWixSearchAttributes(XmlTextWriter writer)
46 {
47 writer.WriteAttributeString("Id", this.Id);
48 writer.WriteAttributeString("Variable", this.Variable);
49 if (!String.IsNullOrEmpty(this.Condition))
50 {
51 writer.WriteAttributeString("Condition", this.Condition);
52 }
53 }
54 }
55}
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
15 { 15 {
16 public IBindResult Bind(IBindContext context) 16 public IBindResult Bind(IBindContext context)
17 { 17 {
18 BindBundleCommand command = new BindBundleCommand(context); 18 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
19 //command.DefaultCompressionLevel = context.DefaultCompressionLevel; 19
20 //command.Extensions = context.Extensions; 20 var backendExtensions = extensionManager.GetServices<IBurnBackendExtension>();
21 //command.IntermediateFolder = context.IntermediateFolder; 21
22 //command.Output = context.IntermediateRepresentation; 22 foreach (var extension in backendExtensions)
23 //command.OutputPath = context.OutputPath; 23 {
24 //command.PdbFile = context.OutputPdbPath; 24 extension.PreBackendBind(context);
25 //command.WixVariableResolver = context.WixVariableResolver; 25 }
26
27 var command = new BindBundleCommand(context, backendExtensions);
26 command.Execute(); 28 command.Execute();
27 29
28 var result = context.ServiceProvider.GetService<IBindResult>(); 30 var result = context.ServiceProvider.GetService<IBindResult>();
29 result.FileTransfers = command.FileTransfers; 31 result.FileTransfers = command.FileTransfers;
30 result.TrackedFiles = command.TrackedFiles; 32 result.TrackedFiles = command.TrackedFiles;
31 33
34 foreach (var extension in backendExtensions)
35 {
36 extension.PostBackendBind(result);
37 }
32 return result; 38 return result;
33 } 39 }
34 40
@@ -53,10 +59,10 @@ namespace WixToolset.Core.Burn
53 59
54 public Intermediate Unbind(IUnbindContext context) 60 public Intermediate Unbind(IUnbindContext context)
55 { 61 {
56 string uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); 62 var uxExtractPath = Path.Combine(context.ExportBasePath, "UX");
57 string acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); 63 var acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer");
58 64
59 using (BurnReader reader = BurnReader.Open(context.InputFilePath)) 65 using (var reader = BurnReader.Open(context.InputFilePath))
60 { 66 {
61 reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder); 67 reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder);
62 reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder); 68 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
7 using System.Diagnostics; 7 using System.Diagnostics;
8 using System.Linq; 8 using System.Linq;
9 using WixToolset.Data; 9 using WixToolset.Data;
10 using WixToolset.Data.Tuples;
10 11
11 internal class AutomaticallySlipstreamPatchesCommand 12 internal class AutomaticallySlipstreamPatchesCommand
12 { 13 {
13#if TODO 14 public AutomaticallySlipstreamPatchesCommand(IntermediateSection section, ICollection<PackageFacade> packageFacades)
14 public IEnumerable<PackageFacade> PackageFacades { private get; set; } 15 {
16 this.Section = section;
17 this.PackageFacades = packageFacades;
18 }
15 19
16 public Table WixBundlePatchTargetCodeTable { private get; set; } 20 private IntermediateSection Section { get; }
17 21
18 public Table SlipstreamMspTable { private get; set; } 22 private IEnumerable<PackageFacade> PackageFacades { get; }
19 23
20 public void Execute() 24 public void Execute()
21 { 25 {
22 List<WixBundleMsiPackageRow> msiPackages = new List<WixBundleMsiPackageRow>(); 26 var msiPackages = new List<WixBundleMsiPackageTuple>();
23 Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsProductCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>(); 27 var targetsProductCode = new Dictionary<string, List<WixBundlePatchTargetCodeTuple>>();
24 Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsUpgradeCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>(); 28 var targetsUpgradeCode = new Dictionary<string, List<WixBundlePatchTargetCodeTuple>>();
25 29
26 foreach (PackageFacade facade in this.PackageFacades) 30 foreach (var facade in this.PackageFacades)
27 { 31 {
28 if (WixBundlePackageType.Msi == facade.Package.Type) 32 // Keep track of all MSI packages.
33 if (facade.SpecificPackageTuple is WixBundleMsiPackageTuple msiPackage)
29 { 34 {
30 // Keep track of all MSI packages. 35 msiPackages.Add(msiPackage);
31 msiPackages.Add(facade.MsiPackage);
32 } 36 }
33 else if (WixBundlePackageType.Msp == facade.Package.Type && facade.MspPackage.Slipstream) 37 else if (facade.SpecificPackageTuple is WixBundleMspPackageTuple mspPackage && mspPackage.Slipstream)
34 { 38 {
35 IEnumerable<WixBundlePatchTargetCodeRow> patchTargetCodeRows = this.WixBundlePatchTargetCodeTable.RowsAs<WixBundlePatchTargetCodeRow>().Where(r => r.MspPackageId == facade.Package.WixChainItemId); 39 var patchTargetCodeTuples = this.Section.Tuples
40 .OfType<WixBundlePatchTargetCodeTuple>()
41 .Where(r => r.PackageRef == facade.PackageId);
36 42
37 // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. 43 // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs.
38 foreach (WixBundlePatchTargetCodeRow row in patchTargetCodeRows) 44 foreach (var tuple in patchTargetCodeTuples)
39 { 45 {
40 if (row.TargetsProductCode) 46 if (tuple.TargetsProductCode)
41 { 47 {
42 List<WixBundlePatchTargetCodeRow> rows; 48 if (!targetsProductCode.TryGetValue(tuple.TargetCode, out var tuples))
43 if (!targetsProductCode.TryGetValue(row.TargetCode, out rows))
44 { 49 {
45 rows = new List<WixBundlePatchTargetCodeRow>(); 50 tuples = new List<WixBundlePatchTargetCodeTuple>();
46 targetsProductCode.Add(row.TargetCode, rows); 51 targetsProductCode.Add(tuple.TargetCode, tuples);
47 } 52 }
48 53
49 rows.Add(row); 54 tuples.Add(tuple);
50 } 55 }
51 else if (row.TargetsUpgradeCode) 56 else if (tuple.TargetsUpgradeCode)
52 { 57 {
53 List<WixBundlePatchTargetCodeRow> rows; 58 if (!targetsUpgradeCode.TryGetValue(tuple.TargetCode, out var tuples))
54 if (!targetsUpgradeCode.TryGetValue(row.TargetCode, out rows))
55 { 59 {
56 rows = new List<WixBundlePatchTargetCodeRow>(); 60 tuples = new List<WixBundlePatchTargetCodeTuple>();
57 targetsUpgradeCode.Add(row.TargetCode, rows); 61 targetsUpgradeCode.Add(tuple.TargetCode, tuples);
58 } 62 }
59 } 63 }
60 } 64 }
61 } 65 }
62 } 66 }
63 67
64 RowIndexedList<Row> slipstreamMspRows = new RowIndexedList<Row>(SlipstreamMspTable); 68 var slipstreamMspIds = new HashSet<string>();
65 69
66 // Loop through the MSI and slipstream patches targeting it. 70 // Loop through the MSI and slipstream patches targeting it.
67 foreach (WixBundleMsiPackageRow msi in msiPackages) 71 foreach (var msi in msiPackages)
68 { 72 {
69 List<WixBundlePatchTargetCodeRow> rows; 73 if (targetsProductCode.TryGetValue(msi.ProductCode, out var tuples))
70 if (targetsProductCode.TryGetValue(msi.ProductCode, out rows))
71 { 74 {
72 foreach (WixBundlePatchTargetCodeRow row in rows) 75 foreach (var tuple in tuples)
73 { 76 {
74 Debug.Assert(row.TargetsProductCode); 77 Debug.Assert(tuple.TargetsProductCode);
75 Debug.Assert(!row.TargetsUpgradeCode); 78 Debug.Assert(!tuple.TargetsUpgradeCode);
76
77 Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false);
78 slipstreamMspRow[0] = msi.ChainPackageId;
79 slipstreamMspRow[1] = row.MspPackageId;
80 79
81 if (slipstreamMspRows.TryAdd(slipstreamMspRow)) 80 this.TryAddSlipstreamTuple(slipstreamMspIds, msi, tuple);
82 {
83 SlipstreamMspTable.Rows.Add(slipstreamMspRow);
84 }
85 } 81 }
86
87 rows = null;
88 } 82 }
89 83
90 if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out rows)) 84 if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out tuples))
91 { 85 {
92 foreach (WixBundlePatchTargetCodeRow row in rows) 86 foreach (var tuple in tuples)
93 { 87 {
94 Debug.Assert(!row.TargetsProductCode); 88 Debug.Assert(!tuple.TargetsProductCode);
95 Debug.Assert(row.TargetsUpgradeCode); 89 Debug.Assert(tuple.TargetsUpgradeCode);
96
97 Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false);
98 slipstreamMspRow[0] = msi.ChainPackageId;
99 slipstreamMspRow[1] = row.MspPackageId;
100 90
101 if (slipstreamMspRows.TryAdd(slipstreamMspRow)) 91 this.TryAddSlipstreamTuple(slipstreamMspIds, msi, tuple);
102 {
103 SlipstreamMspTable.Rows.Add(slipstreamMspRow);
104 }
105 } 92 }
106 93
107 rows = null; 94 tuples = null;
108 } 95 }
109 } 96 }
110 } 97 }
111#endif 98
99 private bool TryAddSlipstreamTuple(HashSet<string> slipstreamMspIds, WixBundleMsiPackageTuple msiPackage, WixBundlePatchTargetCodeTuple patchTargetCode)
100 {
101 var id = new Identifier(AccessModifier.Private, msiPackage.Id.Id, patchTargetCode.PackageRef);
102
103 if (slipstreamMspIds.Add(id.Id))
104 {
105 var slipstreamTuple = new WixBundleSlipstreamMspTuple(patchTargetCode.SourceLineNumbers)
106 {
107 TargetPackageRef = msiPackage.Id.Id,
108 MspPackageRef = patchTargetCode.PackageRef
109 };
110
111 //var slipstreamMspRow = SlipstreamMspTable.CreateRow(tuple.SourceLineNumbers, false);
112 //slipstreamMspRow[0] = msi.ChainPackageId;
113 //slipstreamMspRow[1] = tuple.MspPackageId;
114
115 this.Section.Tuples.Add(slipstreamTuple);
116 return true;
117 }
118
119 return false;
120 }
112 } 121 }
113} 122}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System.IO;
6 using System.Security.Cryptography;
7 using System.Text;
8
9 internal static class BundleHashAlgorithm
10 {
11 public static string Hash(FileInfo fileInfo)
12 {
13 byte[] hashBytes;
14
15 using (var managed = new SHA1Managed())
16 using (var stream = fileInfo.OpenRead())
17 {
18 hashBytes = managed.ComputeHash(stream);
19 }
20
21 var sb = new StringBuilder(hashBytes.Length * 2);
22 for (var i = 0; i < hashBytes.Length; i++)
23 {
24 sb.AppendFormat("{0:X2}", hashBytes[i]);
25 }
26
27 return sb.ToString();
28 }
29 }
30}
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
128 128
129 public void Dispose() 129 public void Dispose()
130 { 130 {
131 Dispose(true); 131 this.Dispose(true);
132 132
133 GC.SuppressFinalize(this); 133 GC.SuppressFinalize(this);
134 } 134 }
@@ -238,7 +238,7 @@ namespace WixToolset.Core.Burn.Bundles
238 { 238 {
239 if (UInt32.MaxValue == this.wixburnDataOffset) 239 if (UInt32.MaxValue == this.wixburnDataOffset)
240 { 240 {
241 if (!EnsureNTHeader(reader)) 241 if (!this.EnsureNTHeader(reader))
242 { 242 {
243 return false; 243 return false;
244 } 244 }
@@ -286,7 +286,7 @@ namespace WixToolset.Core.Burn.Bundles
286 { 286 {
287 if (UInt32.MaxValue == this.firstSectionOffset) 287 if (UInt32.MaxValue == this.firstSectionOffset)
288 { 288 {
289 if (!EnsureDosHeader(reader)) 289 if (!this.EnsureDosHeader(reader))
290 { 290 {
291 return false; 291 return false;
292 } 292 }
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
67 /// <param name="stubSize">Size of the stub engine "burn.exe".</param> 67 /// <param name="stubSize">Size of the stub engine "burn.exe".</param>
68 /// <param name="bundleId">Unique identifier for this bundle.</param> 68 /// <param name="bundleId">Unique identifier for this bundle.</param>
69 /// <returns></returns> 69 /// <returns></returns>
70 public bool InitializeBundleSectionData(long stubSize, Guid bundleId) 70 public bool InitializeBundleSectionData(long stubSize, string bundleId)
71 { 71 {
72 if (this.invalidBundle) 72 if (this.invalidBundle)
73 { 73 {
74 return false; 74 return false;
75 } 75 }
76 76
77 var bundleGuid = Guid.Parse(bundleId);
78
77 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); 79 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC);
78 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); 80 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION);
79 81
80 this.messaging.Write(VerboseMessages.BundleGuid(bundleId.ToString("B"))); 82 this.messaging.Write(VerboseMessages.BundleGuid(bundleId));
81 this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); 83 this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin);
82 this.binaryWriter.Write(bundleId.ToByteArray()); 84 this.binaryWriter.Write(bundleGuid.ToByteArray());
83 85
84 this.StubSize = (uint)stubSize; 86 this.StubSize = (uint)stubSize;
85 87
@@ -146,7 +148,7 @@ namespace WixToolset.Core.Burn.Bundles
146 return false; 148 return false;
147 } 149 }
148 150
149 return AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); 151 return this.AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount);
150 } 152 }
151 153
152 public void RememberThenResetSignature() 154 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
7 using System.Diagnostics; 7 using System.Diagnostics;
8 using System.Globalization; 8 using System.Globalization;
9 using System.IO; 9 using System.IO;
10 using System.Linq;
10 using System.Text; 11 using System.Text;
11 using System.Xml; 12 using System.Xml;
12 using WixToolset.Data; 13 using WixToolset.Data;
14 using WixToolset.Data.Burn;
15 using WixToolset.Data.Tuples;
13 16
14 internal class CreateBootstrapperApplicationManifestCommand 17 internal class CreateBootstrapperApplicationManifestCommand
15 { 18 {
16#if TODO 19 public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<PackageFacade> chainPackages, int lastUXPayloadIndex, Dictionary<string, WixBundlePayloadTuple> payloadTuples, string intermediateFolder)
17 public WixBundleRow BundleRow { private get; set; } 20 {
18 21 this.Section = section;
19 public IEnumerable<PackageFacade> ChainPackages { private get; set; } 22 this.BundleTuple = bundleTuple;
23 this.ChainPackages = chainPackages;
24 this.LastUXPayloadIndex = lastUXPayloadIndex;
25 this.Payloads = payloadTuples;
26 this.IntermediateFolder = intermediateFolder;
27 }
20 28
21 public int LastUXPayloadIndex { private get; set; } 29 private IntermediateSection Section { get; }
22 30
23 public IEnumerable<WixBundleMsiFeatureRow> MsiFeatures { private get; set; } 31 private WixBundleTuple BundleTuple { get; }
24 32
25 public Output Output { private get; set; } 33 private IEnumerable<PackageFacade> ChainPackages { get; }
26 34
27 public RowDictionary<WixBundlePayloadRow> Payloads { private get; set; } 35 private int LastUXPayloadIndex { get; }
28 36
29 public TableDefinitionCollection TableDefinitions { private get; set; } 37 private Dictionary<string, WixBundlePayloadTuple> Payloads { get; }
30 38
31 public string TempFilesLocation { private get; set; } 39 private string IntermediateFolder { get; }
32 40
33 public WixBundlePayloadRow BootstrapperApplicationManifestPayloadRow { get; private set; } 41 public WixBundlePayloadTuple BootstrapperApplicationManifestPayloadRow { get; private set; }
34 42
35 public void Execute() 43 public void Execute()
36 { 44 {
37 this.GenerateBAManifestBundleTables(); 45 var baManifestPath = this.CreateBootstrapperApplicationManifest();
38 46
39 this.GenerateBAManifestMsiFeatureTables(); 47 this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(baManifestPath);
48 }
40 49
41 this.GenerateBAManifestPackageTables(); 50 private string CreateBootstrapperApplicationManifest()
51 {
52 var path = Path.Combine(this.IntermediateFolder, "wix-badata.xml");
42 53
43 this.GenerateBAManifestPayloadTables(); 54 Directory.CreateDirectory(Path.GetDirectoryName(path));
44 55
45 string baManifestPath = Path.Combine(this.TempFilesLocation, "wix-badata.xml"); 56 using (var writer = new XmlTextWriter(path, Encoding.Unicode))
57 {
58 writer.Formatting = Formatting.Indented;
59 writer.WriteStartDocument();
60 writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData");
46 61
47 this.CreateBootstrapperApplicationManifest(baManifestPath); 62 this.WriteBundleInfo(writer);
48 63
49 this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(baManifestPath); 64 this.WritePackageInfo(writer);
65
66 this.WriteFeatureInfo(writer);
67
68 this.WritePayloadInfo(writer);
69
70 this.WriteCustomBootstrapperApplicationData(writer);
71
72 writer.WriteEndElement();
73 writer.WriteEndDocument();
74 }
75
76 return path;
50 } 77 }
51 78
52 private void GenerateBAManifestBundleTables() 79 private void WriteBundleInfo(XmlTextWriter writer)
53 { 80 {
54 Table wixBundlePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleProperties"]); 81 writer.WriteStartElement("WixBundleProperties");
55 82
56 Row row = wixBundlePropertiesTable.CreateRow(this.BundleRow.SourceLineNumbers); 83 writer.WriteAttributeString("DisplayName", this.BundleTuple.Name);
57 row[0] = this.BundleRow.Name; 84 writer.WriteAttributeString("LogPathVariable", this.BundleTuple.LogPathVariable);
58 row[1] = this.BundleRow.LogPathVariable; 85 writer.WriteAttributeString("Compressed", this.BundleTuple.Compressed == true ? "yes" : "no");
59 row[2] = (YesNoDefaultType.Yes == this.BundleRow.Compressed) ? "yes" : "no"; 86 writer.WriteAttributeString("BundleId", this.BundleTuple.BundleId.ToUpperInvariant());
60 row[3] = this.BundleRow.BundleId.ToString("B"); 87 writer.WriteAttributeString("UpgradeCode", this.BundleTuple.UpgradeCode);
61 row[4] = this.BundleRow.UpgradeCode; 88 writer.WriteAttributeString("PerMachine", this.BundleTuple.PerMachine ? "yes" : "no");
62 row[5] = this.BundleRow.PerMachine ? "yes" : "no"; 89
90 writer.WriteEndElement();
63 } 91 }
64 92
65 private void GenerateBAManifestPackageTables() 93 private void WritePackageInfo(XmlTextWriter writer)
66 { 94 {
67 Table wixPackagePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageProperties"]); 95 foreach (var package in this.ChainPackages)
68
69 foreach (PackageFacade package in this.ChainPackages)
70 { 96 {
71 WixBundlePayloadRow packagePayload = this.Payloads[package.Package.PackagePayload]; 97 var packagePayload = this.Payloads[package.PackageTuple.PayloadRef];
72 98
73 Row row = wixPackagePropertiesTable.CreateRow(package.Package.SourceLineNumbers); 99 var size = package.PackageTuple.Size.ToString(CultureInfo.InvariantCulture);
74 row[0] = package.Package.WixChainItemId; 100
75 row[1] = (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"; 101 writer.WriteStartElement("WixBundleProperties");
76 row[2] = package.Package.DisplayName; 102
77 row[3] = package.Package.Description; 103 writer.WriteAttributeString("Package", package.PackageId);
78 row[4] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // TODO: DownloadSize (compressed) (what does this mean when it's embedded?) 104 writer.WriteAttributeString("Vital", package.PackageTuple.Vital == true ? "yes" : "no");
79 row[5] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // Package.Size (uncompressed) 105 writer.WriteAttributeString("DisplayName", package.PackageTuple.DisplayName);
80 row[6] = package.Package.InstallSize.Value.ToString(CultureInfo.InvariantCulture); // InstallSize (required disk space) 106 writer.WriteAttributeString("Description", package.PackageTuple.Description);
81 row[7] = package.Package.Type.ToString(); 107 writer.WriteAttributeString("DownloadSize", size);
82 row[8] = package.Package.Permanent ? "yes" : "no"; 108 writer.WriteAttributeString("PackageSize", size);
83 row[9] = package.Package.LogPathVariable; 109 writer.WriteAttributeString("InstalledSize", package.PackageTuple.InstallSize?.ToString(CultureInfo.InvariantCulture) ?? size);
84 row[10] = package.Package.RollbackLogPathVariable; 110 writer.WriteAttributeString("PackageType", package.PackageTuple.Type.ToString());
85 row[11] = (PackagingType.Embedded == packagePayload.Packaging) ? "yes" : "no"; 111 writer.WriteAttributeString("Permanent", package.PackageTuple.Permanent ? "yes" : "no");
86 112 writer.WriteAttributeString("LogPathVariable", package.PackageTuple.LogPathVariable);
87 if (WixBundlePackageType.Msi == package.Package.Type) 113 writer.WriteAttributeString("RollbackLogPathVariable", package.PackageTuple.RollbackLogPathVariable);
114 writer.WriteAttributeString("Compressed", packagePayload.Compressed == true ? "yes" : "no");
115
116 if (package.SpecificPackageTuple is WixBundleMsiPackageTuple msiPackage)
88 { 117 {
89 row[12] = package.MsiPackage.DisplayInternalUI ? "yes" : "no"; 118 writer.WriteAttributeString("DisplayInternalUI", msiPackage.DisplayInternalUI ? "yes" : "no");
90 119
91 if (!String.IsNullOrEmpty(package.MsiPackage.ProductCode)) 120 if (!String.IsNullOrEmpty(msiPackage.ProductCode))
92 { 121 {
93 row[13] = package.MsiPackage.ProductCode; 122 writer.WriteAttributeString("ProductCode", msiPackage.ProductCode);
94 } 123 }
95 124
96 if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) 125 if (!String.IsNullOrEmpty(msiPackage.UpgradeCode))
97 { 126 {
98 row[14] = package.MsiPackage.UpgradeCode; 127 writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode);
99 } 128 }
100 } 129 }
101 else if (WixBundlePackageType.Msp == package.Package.Type) 130 else if (package.SpecificPackageTuple is WixBundleMspPackageTuple mspPackage)
102 { 131 {
103 row[12] = package.MspPackage.DisplayInternalUI ? "yes" : "no"; 132 writer.WriteAttributeString("DisplayInternalUI", mspPackage.DisplayInternalUI ? "yes" : "no");
104 133
105 if (!String.IsNullOrEmpty(package.MspPackage.PatchCode)) 134 if (!String.IsNullOrEmpty(mspPackage.PatchCode))
106 { 135 {
107 row[13] = package.MspPackage.PatchCode; 136 writer.WriteAttributeString("ProductCode", mspPackage.PatchCode);
108 } 137 }
109 } 138 }
110 139
111 if (!String.IsNullOrEmpty(package.Package.Version)) 140 if (!String.IsNullOrEmpty(package.PackageTuple.Version))
112 { 141 {
113 row[15] = package.Package.Version; 142 writer.WriteAttributeString("Version", package.PackageTuple.Version);
114 } 143 }
115 144
116 if (!String.IsNullOrEmpty(package.Package.InstallCondition)) 145 if (!String.IsNullOrEmpty(package.PackageTuple.InstallCondition))
117 { 146 {
118 row[16] = package.Package.InstallCondition; 147 writer.WriteAttributeString("InstallCondition", package.PackageTuple.InstallCondition);
119 } 148 }
120 149
121 switch (package.Package.Cache) 150 switch (package.PackageTuple.Cache)
122 { 151 {
123 case YesNoAlwaysType.No: 152 case YesNoAlwaysType.No:
124 row[17] = "no"; 153 writer.WriteAttributeString("Cache", "no");
125 break; 154 break;
126 case YesNoAlwaysType.Yes: 155 case YesNoAlwaysType.Yes:
127 row[17] = "yes"; 156 writer.WriteAttributeString("Cache", "yes");
128 break; 157 break;
129 case YesNoAlwaysType.Always: 158 case YesNoAlwaysType.Always:
130 row[17] = "always"; 159 writer.WriteAttributeString("Cache", "always");
131 break; 160 break;
132 } 161 }
162
163 writer.WriteEndElement();
133 } 164 }
134 } 165 }
135 166
136 private void GenerateBAManifestMsiFeatureTables() 167 private void WriteFeatureInfo(XmlTextWriter writer)
137 { 168 {
138 Table wixPackageFeatureInfoTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageFeatureInfo"]); 169 var featureTuples = this.Section.Tuples.OfType<WixBundleMsiFeatureTuple>();
139 170
140 foreach (WixBundleMsiFeatureRow feature in this.MsiFeatures) 171 foreach (var featureTuple in featureTuples)
141 { 172 {
142 Row row = wixPackageFeatureInfoTable.CreateRow(feature.SourceLineNumbers); 173 writer.WriteStartElement("WixPackageFeatureInfo");
143 row[0] = feature.ChainPackageId; 174
144 row[1] = feature.Name; 175 writer.WriteAttributeString("Package", featureTuple.PackageRef);
145 row[2] = Convert.ToString(feature.Size, CultureInfo.InvariantCulture); 176 writer.WriteAttributeString("Feature", featureTuple.Name);
146 row[3] = feature.Parent; 177 writer.WriteAttributeString("Size", featureTuple.Size.ToString(CultureInfo.InvariantCulture));
147 row[4] = feature.Title; 178 writer.WriteAttributeString("Parent", featureTuple.Parent);
148 row[5] = feature.Description; 179 writer.WriteAttributeString("Title", featureTuple.Title);
149 row[6] = Convert.ToString(feature.Display, CultureInfo.InvariantCulture); 180 writer.WriteAttributeString("Description", featureTuple.Description);
150 row[7] = Convert.ToString(feature.Level, CultureInfo.InvariantCulture); 181 writer.WriteAttributeString("Display", featureTuple.Display.ToString(CultureInfo.InvariantCulture));
151 row[8] = feature.Directory; 182 writer.WriteAttributeString("Level", featureTuple.Level.ToString(CultureInfo.InvariantCulture));
152 row[9] = Convert.ToString(feature.Attributes, CultureInfo.InvariantCulture); 183 writer.WriteAttributeString("Directory", featureTuple.Directory);
153 } 184 writer.WriteAttributeString("Attributes", featureTuple.Attributes.ToString(CultureInfo.InvariantCulture));
154 185
186 writer.WriteEndElement();
187 }
155 } 188 }
156 189
157 private void GenerateBAManifestPayloadTables() 190 private void WritePayloadInfo(XmlTextWriter writer)
158 { 191 {
159 Table wixPayloadPropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPayloadProperties"]); 192 var payloadTuples = this.Section.Tuples.OfType<WixBundlePayloadTuple>();
160 193
161 foreach (WixBundlePayloadRow payload in this.Payloads.Values) 194 foreach (var payloadTuple in payloadTuples)
162 { 195 {
163 WixPayloadPropertiesRow row = (WixPayloadPropertiesRow)wixPayloadPropertiesTable.CreateRow(payload.SourceLineNumbers); 196 writer.WriteStartElement("WixPackageFeatureInfo");
164 row.Id = payload.Id; 197
165 row.Package = payload.Package; 198 writer.WriteAttributeString("Id", payloadTuple.Id.Id);
166 row.Container = payload.Container; 199 writer.WriteAttributeString("Package", payloadTuple.PackageRef);
167 row.Name = payload.Name; 200 writer.WriteAttributeString("Container", payloadTuple.ContainerRef);
168 row.Size = payload.FileSize.ToString(); 201 writer.WriteAttributeString("Name", payloadTuple.Name);
169 row.DownloadUrl = payload.DownloadUrl; 202 writer.WriteAttributeString("Size", payloadTuple.FileSize.ToString(CultureInfo.InvariantCulture));
170 row.LayoutOnly = payload.LayoutOnly ? "yes" : "no"; 203 writer.WriteAttributeString("DownloadUrl", payloadTuple.DownloadUrl);
204 writer.WriteAttributeString("LayoutOnly", payloadTuple.LayoutOnly ? "yes" : "no");
205
206 writer.WriteEndElement();
171 } 207 }
172 } 208 }
173 209
174 private void CreateBootstrapperApplicationManifest(string path) 210 private void WriteCustomBootstrapperApplicationData(XmlTextWriter writer)
175 { 211 {
176 using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.Unicode)) 212 var dataTuplesGroupedByDefinitionName = this.Section.Tuples
213 .Where(t => t.Definition.HasTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag))
214 .GroupBy(t => t.Definition);
215
216 foreach (var group in dataTuplesGroupedByDefinitionName)
177 { 217 {
178 writer.Formatting = Formatting.Indented; 218 var definition = group.Key;
179 writer.WriteStartDocument();
180 writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/2010/BootstrapperApplicationData");
181 219
182 foreach (Table table in this.Output.Tables) 220 // We simply assert that the table (and field) name is valid, because
183 { 221 // this is up to the extension developer to get right. An author will
184 if (table.Definition.BootstrapperApplicationData) 222 // only affect the attribute value, and that will get properly escaped.
185 {
186 // We simply assert that the table (and field) name is valid, because
187 // this is up to the extension developer to get right. An author will
188 // only affect the attribute value, and that will get properly escaped.
189#if DEBUG 223#if DEBUG
190 Debug.Assert(Common.IsIdentifier(table.Name)); 224 Debug.Assert(Common.IsIdentifier(definition.Name));
191 foreach (ColumnDefinition column in table.Definition.Columns) 225 foreach (var fieldDef in definition.FieldDefinitions)
192 { 226 {
193 Debug.Assert(Common.IsIdentifier(column.Name)); 227 Debug.Assert(Common.IsIdentifier(fieldDef.Name));
194 } 228 }
195#endif // DEBUG 229#endif // DEBUG
196 230
197 foreach (Row row in table.Rows) 231 foreach (var row in group)
198 { 232 {
199 writer.WriteStartElement(table.Name); 233 writer.WriteStartElement(definition.Name);
200
201 foreach (Field field in row.Fields)
202 {
203 if (null != field.Data)
204 {
205 writer.WriteAttributeString(field.Column.Name, field.Data.ToString());
206 }
207 }
208 234
209 writer.WriteEndElement(); 235 foreach (var field in row.Fields)
236 {
237 if (!field.IsNull())
238 {
239 writer.WriteAttributeString(field.Definition.Name, field.AsString());
210 } 240 }
211 } 241 }
212 }
213 242
214 writer.WriteEndElement(); 243 writer.WriteEndElement();
215 writer.WriteEndDocument(); 244 }
216 } 245 }
217 } 246 }
218 247
219 private WixBundlePayloadRow CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) 248 private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath)
220 { 249 {
221 Table payloadTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePayload"]); 250 var generatedId = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml");
222 WixBundlePayloadRow row = (WixBundlePayloadRow)payloadTable.CreateRow(this.BundleRow.SourceLineNumbers); 251
223 row.Id = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); 252 var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId))
224 row.Name = "BootstrapperApplicationData.xml"; 253 {
225 row.SourceFile = baManifestPath; 254 Name = "BootstrapperApplicationData.xml",
226 row.Compressed = YesNoDefaultType.Yes; 255 SourceFile = new IntermediateFieldPathValue { Path = baManifestPath },
227 row.UnresolvedSourceFile = baManifestPath; 256 Compressed = true,
228 row.Container = Compiler.BurnUXContainerId; 257 UnresolvedSourceFile = baManifestPath,
229 row.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex); 258 ContainerRef = BurnConstants.BurnUXContainerName,
230 row.Packaging = PackagingType.Embedded; 259 EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex),
260 Packaging = PackagingType.Embedded,
261 };
262
263 var fileInfo = new FileInfo(baManifestPath);
231 264
232 FileInfo fileInfo = new FileInfo(row.SourceFile); 265 tuple.FileSize = (int)fileInfo.Length;
233 266
234 row.FileSize = (int)fileInfo.Length; 267 tuple.Hash = BundleHashAlgorithm.Hash(fileInfo);
235 268
236 row.Hash = Common.GetFileHash(fileInfo.FullName); 269 this.Section.Tuples.Add(tuple);
237 270
238 return row; 271 return tuple;
239 } 272 }
240#endif
241 } 273 }
242} 274}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Reflection;
9 using WixToolset.Data;
10 using WixToolset.Data.Burn;
11 using WixToolset.Data.Tuples;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 internal class CreateBundleExeCommand
16 {
17 public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBundleTuple bundleTuple, WixBundleContainerTuple uxContainer, IEnumerable<WixBundleContainerTuple> containers, string burnStubPath)
18 {
19 this.Messaging = messaging;
20 this.BackendHelper = backendHelper;
21 this.IntermediateFolder = intermediateFolder;
22 this.OutputPath = outputPath;
23 this.BundleTuple = bundleTuple;
24 this.UXContainer = uxContainer;
25 this.Containers = containers;
26 this.BurnStubPath = burnStubPath;
27 }
28
29 public IFileTransfer Transfer { get; private set; }
30
31 private IMessaging Messaging { get; }
32
33 private IBackendHelper BackendHelper { get; }
34
35 private string IntermediateFolder { get; }
36
37 private string OutputPath { get; }
38
39 private WixBundleTuple BundleTuple { get; }
40
41 private WixBundleContainerTuple UXContainer { get; }
42
43 private IEnumerable<WixBundleContainerTuple> Containers { get; }
44
45 private string BurnStubPath { get; }
46
47 public void Execute()
48 {
49 var bundleFilename = Path.GetFileName(this.OutputPath);
50
51 // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note
52 // that today, the x64 Burn uses the x86 stub.
53
54 var stubFile = this.BurnStubPath;
55
56 if (String.IsNullOrEmpty(stubFile))
57 {
58 var stubPlatform = (Platform.X64 == this.BundleTuple.Platform) ? "x86" : this.BundleTuple.Platform.ToString();
59
60 stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe");
61 }
62
63 var bundleTempPath = Path.Combine(this.IntermediateFolder, bundleFilename);
64
65 this.Messaging.Write(VerboseMessages.GeneratingBundle(bundleTempPath, stubFile));
66
67 if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase))
68 {
69 this.Messaging.Write(ErrorMessages.InsecureBundleFilename(bundleFilename));
70 }
71
72 this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleTuple.SourceLineNumbers);
73
74 File.Copy(stubFile, bundleTempPath, true);
75 File.SetAttributes(bundleTempPath, FileAttributes.Normal);
76
77 this.UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleTuple);
78
79 // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers
80 // if they should be attached.
81 using (var writer = BurnWriter.Open(this.Messaging, bundleTempPath))
82 {
83 var burnStubFile = new FileInfo(bundleTempPath);
84 writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleTuple.BundleId);
85
86 // Always attach the UX container first
87 writer.AppendContainer(this.UXContainer.WorkingPath, BurnWriter.Container.UX);
88
89 // Now append all other attached containers
90 foreach (var container in this.Containers)
91 {
92 if (ContainerType.Attached == container.Type)
93 {
94 // The container was only created if it had payloads.
95 if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id)
96 {
97 writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached);
98 }
99 }
100 }
101 }
102 }
103
104 private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleTuple bundleInfo)
105 {
106 var resources = new Dtf.Resources.ResourceCollection();
107 var version = new Dtf.Resources.VersionResource("#1", 1033);
108
109 version.Load(bundleTempPath);
110 resources.Add(version);
111
112 // Ensure the bundle info provides a full four part version.
113 var fourPartVersion = new Version(bundleInfo.Version);
114 var major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major;
115 var minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor;
116 var build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build;
117 var revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision;
118
119 if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision)
120 {
121 throw new WixException(ErrorMessages.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version));
122 }
123
124 fourPartVersion = new Version(major, minor, build, revision);
125 version.FileVersion = fourPartVersion;
126 version.ProductVersion = fourPartVersion;
127
128 var strings = version[1033];
129 strings["LegalCopyright"] = bundleInfo.Copyright;
130 strings["OriginalFilename"] = Path.GetFileName(outputPath);
131 strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
132 strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
133
134 if (!String.IsNullOrEmpty(bundleInfo.Name))
135 {
136 strings["ProductName"] = bundleInfo.Name;
137 strings["FileDescription"] = bundleInfo.Name;
138 }
139
140 if (!String.IsNullOrEmpty(bundleInfo.Manufacturer))
141 {
142 strings["CompanyName"] = bundleInfo.Manufacturer;
143 }
144 else
145 {
146 strings["CompanyName"] = String.Empty;
147 }
148
149 if (!String.IsNullOrEmpty(bundleInfo.IconSourceFile))
150 {
151 var iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033);
152 iconGroup.ReadFromFile(bundleInfo.IconSourceFile);
153 resources.Add(iconGroup);
154
155 foreach (var icon in iconGroup.Icons)
156 {
157 resources.Add(icon);
158 }
159 }
160
161 if (!String.IsNullOrEmpty(bundleInfo.SplashScreenSourceFile))
162 {
163 var bitmap = new Dtf.Resources.BitmapResource("#1", 1033);
164 bitmap.ReadFromFile(bundleInfo.SplashScreenSourceFile);
165 resources.Add(bitmap);
166 }
167
168 resources.Save(bundleTempPath);
169 }
170 }
171}
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
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Diagnostics; 7 using System.Diagnostics;
8 using System.Globalization; 8 using System.Globalization;
9 using System.IO;
9 using System.Linq; 10 using System.Linq;
10 using System.Text; 11 using System.Text;
11 using System.Xml; 12 using System.Xml;
12 using WixToolset.Data; 13 using WixToolset.Data;
14 using WixToolset.Data.Burn;
15 using WixToolset.Data.Tuples;
13 using WixToolset.Extensibility; 16 using WixToolset.Extensibility;
17 using WixToolset.Extensibility.Services;
14 18
15 internal class CreateBurnManifestCommand 19 internal class CreateBurnManifestCommand
16 { 20 {
17#if TODO 21 public CreateBurnManifestCommand(IMessaging messaging, IEnumerable<IBurnBackendExtension> backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<WixBundleContainerTuple> containers, WixChainTuple chainTuple, IEnumerable<PackageFacade> orderedPackages, IEnumerable<WixBundleRollbackBoundaryTuple> boundaries, IEnumerable<WixBundlePayloadTuple> uxPayloads, Dictionary<string, WixBundlePayloadTuple> allPayloadsById, IEnumerable<SearchFacade> orderedSearches, IEnumerable<WixBundleCatalogTuple> catalogs, string intermediateFolder)
18 public IEnumerable<IBurnBackendExtension> BackendExtensions { private get; set; } 22 {
23 this.Messaging = messaging;
24 this.BackendExtensions = backendExtensions;
25 this.ExecutableName = executableName;
26 this.Section = section;
27 this.BundleTuple = bundleTuple;
28 this.Chain = chainTuple;
29 this.Containers = containers;
30 this.OrderedPackages = orderedPackages;
31 this.RollbackBoundaries = boundaries;
32 this.UXContainerPayloads = uxPayloads;
33 this.Payloads = allPayloadsById;
34 this.OrderedSearches = orderedSearches;
35 this.Catalogs = catalogs;
36 this.IntermediateFolder = intermediateFolder;
37 }
38
39 public string OutputPath { get; private set; }
40
41 private IMessaging Messaging { get; }
19 42
20 public Output Output { private get; set; } 43 private IEnumerable<IBurnBackendExtension> BackendExtensions { get; }
21 44
22 public string ExecutableName { private get; set; } 45 private string ExecutableName { get; }
23 46
24 public WixBundleRow BundleInfo { private get; set; } 47 private IntermediateSection Section { get; }
25 48
26 public WixChainRow Chain { private get; set; } 49 private WixBundleTuple BundleTuple { get; }
27 50
28 public string OutputPath { private get; set; } 51 private WixChainTuple Chain { get; }
29 52
30 public IEnumerable<WixBundleRollbackBoundaryRow> RollbackBoundaries { private get; set; } 53 private IEnumerable<WixBundleRollbackBoundaryTuple> RollbackBoundaries { get; }
31 54
32 public IEnumerable<PackageFacade> OrderedPackages { private get; set; } 55 private IEnumerable<PackageFacade> OrderedPackages { get; }
33 56
34 public IEnumerable<WixSearchInfo> OrderedSearches { private get; set; } 57 private IEnumerable<SearchFacade> OrderedSearches { get; }
35 58
36 public Dictionary<string, WixBundlePayloadRow> Payloads { private get; set; } 59 private Dictionary<string, WixBundlePayloadTuple> Payloads { get; }
37 60
38 public Dictionary<string, WixBundleContainerRow> Containers { private get; set; } 61 private IEnumerable<WixBundleContainerTuple> Containers { get; }
39 62
40 public IEnumerable<WixBundlePayloadRow> UXContainerPayloads { private get; set; } 63 private IEnumerable<WixBundlePayloadTuple> UXContainerPayloads { get; }
41 64
42 public IEnumerable<WixBundleCatalogRow> Catalogs { private get; set; } 65 private IEnumerable<WixBundleCatalogTuple> Catalogs { get; }
66
67 private string IntermediateFolder { get; }
43 68
44 public void Execute() 69 public void Execute()
45 { 70 {
46 using (XmlTextWriter writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) 71 this.OutputPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml");
72
73 using (var writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8))
47 { 74 {
48 writer.WriteStartDocument(); 75 writer.WriteStartDocument();
49 76
50 writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); 77 writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace);
51 78
52 // Write the condition, if there is one 79 // Write the condition, if there is one
53 if (null != this.BundleInfo.Condition) 80 if (null != this.BundleTuple.Condition)
54 { 81 {
55 writer.WriteElementString("Condition", this.BundleInfo.Condition); 82 writer.WriteElementString("Condition", this.BundleTuple.Condition);
56 } 83 }
57 84
58 // Write the log element if default logging wasn't disabled. 85 // Write the log element if default logging wasn't disabled.
59 if (!String.IsNullOrEmpty(this.BundleInfo.LogPrefix)) 86 if (!String.IsNullOrEmpty(this.BundleTuple.LogPrefix))
60 { 87 {
61 writer.WriteStartElement("Log"); 88 writer.WriteStartElement("Log");
62 if (!String.IsNullOrEmpty(this.BundleInfo.LogPathVariable)) 89 if (!String.IsNullOrEmpty(this.BundleTuple.LogPathVariable))
63 { 90 {
64 writer.WriteAttributeString("PathVariable", this.BundleInfo.LogPathVariable); 91 writer.WriteAttributeString("PathVariable", this.BundleTuple.LogPathVariable);
65 } 92 }
66 writer.WriteAttributeString("Prefix", this.BundleInfo.LogPrefix); 93 writer.WriteAttributeString("Prefix", this.BundleTuple.LogPrefix);
67 writer.WriteAttributeString("Extension", this.BundleInfo.LogExtension); 94 writer.WriteAttributeString("Extension", this.BundleTuple.LogExtension);
68 writer.WriteEndElement(); 95 writer.WriteEndElement();
69 } 96 }
70 97
71 98
72 // Get update if specified. 99 // Get update if specified.
73 WixBundleUpdateRow updateRow = this.Output.Tables["WixBundleUpdate"].RowsAs<WixBundleUpdateRow>().FirstOrDefault(); 100 var updateTuple = this.Section.Tuples.OfType<WixBundleUpdateTuple>().FirstOrDefault();
74 101
75 if (null != updateRow) 102 if (null != updateTuple)
76 { 103 {
77 writer.WriteStartElement("Update"); 104 writer.WriteStartElement("Update");
78 writer.WriteAttributeString("Location", updateRow.Location); 105 writer.WriteAttributeString("Location", updateTuple.Location);
79 writer.WriteEndElement(); // </Update> 106 writer.WriteEndElement(); // </Update>
80 } 107 }
81 108
@@ -83,23 +110,27 @@ namespace WixToolset.Core.Burn.Bundles
83 110
84 // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates 111 // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates
85 // enumeration in the index row list is not used). 112 // enumeration in the index row list is not used).
86 RowIndexedList<WixRelatedBundleRow> relatedBundles = new RowIndexedList<WixRelatedBundleRow>(this.Output.Tables["WixRelatedBundle"]); 113 var relatedBundles = this.Section.Tuples.OfType<WixRelatedBundleTuple>();
114 var distinctRelatedBundles = new HashSet<string>();
87 115
88 foreach (WixRelatedBundleRow relatedBundle in relatedBundles) 116 foreach (var relatedBundle in relatedBundles)
89 { 117 {
90 writer.WriteStartElement("RelatedBundle"); 118 if (distinctRelatedBundles.Add(relatedBundle.BundleId))
91 writer.WriteAttributeString("Id", relatedBundle.Id); 119 {
92 writer.WriteAttributeString("Action", Convert.ToString(relatedBundle.Action, CultureInfo.InvariantCulture)); 120 writer.WriteStartElement("RelatedBundle");
93 writer.WriteEndElement(); 121 writer.WriteAttributeString("Id", relatedBundle.BundleId);
122 writer.WriteAttributeString("Action", relatedBundle.Action.ToString());
123 writer.WriteEndElement();
124 }
94 } 125 }
95 126
96 // Write the variables 127 // Write the variables
97 IEnumerable<WixBundleVariableRow> variables = this.Output.Tables["WixBundleVariable"].RowsAs<WixBundleVariableRow>(); 128 var variables = this.Section.Tuples.OfType<WixBundleVariableTuple>();
98 129
99 foreach (WixBundleVariableRow variable in variables) 130 foreach (var variable in variables)
100 { 131 {
101 writer.WriteStartElement("Variable"); 132 writer.WriteStartElement("Variable");
102 writer.WriteAttributeString("Id", variable.Id); 133 writer.WriteAttributeString("Id", variable.Id.Id);
103 if (null != variable.Type) 134 if (null != variable.Type)
104 { 135 {
105 writer.WriteAttributeString("Value", variable.Value); 136 writer.WriteAttributeString("Value", variable.Value);
@@ -111,20 +142,20 @@ namespace WixToolset.Core.Burn.Bundles
111 } 142 }
112 143
113 // Write the searches 144 // Write the searches
114 foreach (WixSearchInfo searchinfo in this.OrderedSearches) 145 foreach (var searchinfo in this.OrderedSearches)
115 { 146 {
116 searchinfo.WriteXml(writer); 147 searchinfo.WriteXml(writer);
117 } 148 }
118 149
119 // write the UX element 150 // write the UX element
120 writer.WriteStartElement("UX"); 151 writer.WriteStartElement("UX");
121 if (!String.IsNullOrEmpty(this.BundleInfo.SplashScreenBitmapPath)) 152 if (!String.IsNullOrEmpty(this.BundleTuple.SplashScreenSourceFile))
122 { 153 {
123 writer.WriteAttributeString("SplashScreen", "yes"); 154 writer.WriteAttributeString("SplashScreen", "yes");
124 } 155 }
125 156
126 // write the UX allPayloads... 157 // write the UX allPayloads...
127 foreach (WixBundlePayloadRow payload in this.UXContainerPayloads) 158 foreach (var payload in this.UXContainerPayloads)
128 { 159 {
129 writer.WriteStartElement("Payload"); 160 writer.WriteStartElement("Payload");
130 this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); 161 this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads);
@@ -136,18 +167,18 @@ namespace WixToolset.Core.Burn.Bundles
136 // write the catalog elements 167 // write the catalog elements
137 if (this.Catalogs.Any()) 168 if (this.Catalogs.Any())
138 { 169 {
139 foreach (WixBundleCatalogRow catalog in this.Catalogs) 170 foreach (var catalog in this.Catalogs)
140 { 171 {
141 writer.WriteStartElement("Catalog"); 172 writer.WriteStartElement("Catalog");
142 writer.WriteAttributeString("Id", catalog.Id); 173 writer.WriteAttributeString("Id", catalog.Id.Id);
143 writer.WriteAttributeString("Payload", catalog.Payload); 174 writer.WriteAttributeString("Payload", catalog.PayloadRef);
144 writer.WriteEndElement(); 175 writer.WriteEndElement();
145 } 176 }
146 } 177 }
147 178
148 foreach (WixBundleContainerRow container in this.Containers.Values) 179 foreach (var container in this.Containers)
149 { 180 {
150 if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) 181 if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id)
151 { 182 {
152 writer.WriteStartElement("Container"); 183 writer.WriteStartElement("Container");
153 this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); 184 this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container);
@@ -155,9 +186,9 @@ namespace WixToolset.Core.Burn.Bundles
155 } 186 }
156 } 187 }
157 188
158 foreach (WixBundlePayloadRow payload in this.Payloads.Values) 189 foreach (var payload in this.Payloads.Values)
159 { 190 {
160 if (PackagingType.Embedded == payload.Packaging && Compiler.BurnUXContainerId != payload.Container) 191 if (PackagingType.Embedded == payload.Packaging && BurnConstants.BurnUXContainerName != payload.ContainerRef)
161 { 192 {
162 writer.WriteStartElement("Payload"); 193 writer.WriteStartElement("Payload");
163 this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); 194 this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads);
@@ -171,77 +202,78 @@ namespace WixToolset.Core.Burn.Bundles
171 } 202 }
172 } 203 }
173 204
174 foreach (WixBundleRollbackBoundaryRow rollbackBoundary in this.RollbackBoundaries) 205 foreach (var rollbackBoundary in this.RollbackBoundaries)
175 { 206 {
176 writer.WriteStartElement("RollbackBoundary"); 207 writer.WriteStartElement("RollbackBoundary");
177 writer.WriteAttributeString("Id", rollbackBoundary.ChainPackageId); 208 writer.WriteAttributeString("Id", rollbackBoundary.Id.Id);
178 writer.WriteAttributeString("Vital", YesNoType.Yes == rollbackBoundary.Vital ? "yes" : "no"); 209 writer.WriteAttributeString("Vital", rollbackBoundary.Vital == false ? "no" : "yes");
179 writer.WriteAttributeString("Transaction", YesNoType.Yes == rollbackBoundary.Transaction ? "yes" : "no"); 210 writer.WriteAttributeString("Transaction", rollbackBoundary.Transaction == true ? "yes" : "no");
180 writer.WriteEndElement(); 211 writer.WriteEndElement();
181 } 212 }
182 213
183 // Write the registration information... 214 // Write the registration information...
184 writer.WriteStartElement("Registration"); 215 writer.WriteStartElement("Registration");
185 216
186 writer.WriteAttributeString("Id", this.BundleInfo.BundleId.ToString("B")); 217 writer.WriteAttributeString("Id", this.BundleTuple.BundleId);
187 writer.WriteAttributeString("ExecutableName", this.ExecutableName); 218 writer.WriteAttributeString("ExecutableName", this.ExecutableName);
188 writer.WriteAttributeString("PerMachine", this.BundleInfo.PerMachine ? "yes" : "no"); 219 writer.WriteAttributeString("PerMachine", this.BundleTuple.PerMachine ? "yes" : "no");
189 writer.WriteAttributeString("Tag", this.BundleInfo.Tag); 220 writer.WriteAttributeString("Tag", this.BundleTuple.Tag);
190 writer.WriteAttributeString("Version", this.BundleInfo.Version); 221 writer.WriteAttributeString("Version", this.BundleTuple.Version);
191 writer.WriteAttributeString("ProviderKey", this.BundleInfo.ProviderKey); 222 writer.WriteAttributeString("ProviderKey", this.BundleTuple.ProviderKey);
192 223
193 writer.WriteStartElement("Arp"); 224 writer.WriteStartElement("Arp");
194 writer.WriteAttributeString("Register", (0 < this.BundleInfo.DisableModify && this.BundleInfo.DisableRemove) ? "no" : "yes"); // do not register if disabled modify and remove. 225 writer.WriteAttributeString("Register", (this.BundleTuple.DisableModify || this.BundleTuple.SingleChangeUninstallButton) && this.BundleTuple.DisableRemove ? "no" : "yes"); // do not register if disabled modify and remove.
195 writer.WriteAttributeString("DisplayName", this.BundleInfo.Name); 226 writer.WriteAttributeString("DisplayName", this.BundleTuple.Name);
196 writer.WriteAttributeString("DisplayVersion", this.BundleInfo.Version); 227 writer.WriteAttributeString("DisplayVersion", this.BundleTuple.Version);
197 228
198 if (!String.IsNullOrEmpty(this.BundleInfo.Publisher)) 229 if (!String.IsNullOrEmpty(this.BundleTuple.Manufacturer))
199 { 230 {
200 writer.WriteAttributeString("Publisher", this.BundleInfo.Publisher); 231 writer.WriteAttributeString("Publisher", this.BundleTuple.Manufacturer);
201 } 232 }
202 233
203 if (!String.IsNullOrEmpty(this.BundleInfo.HelpLink)) 234 if (!String.IsNullOrEmpty(this.BundleTuple.HelpUrl))
204 { 235 {
205 writer.WriteAttributeString("HelpLink", this.BundleInfo.HelpLink); 236 writer.WriteAttributeString("HelpLink", this.BundleTuple.HelpUrl);
206 } 237 }
207 238
208 if (!String.IsNullOrEmpty(this.BundleInfo.HelpTelephone)) 239 if (!String.IsNullOrEmpty(this.BundleTuple.HelpTelephone))
209 { 240 {
210 writer.WriteAttributeString("HelpTelephone", this.BundleInfo.HelpTelephone); 241 writer.WriteAttributeString("HelpTelephone", this.BundleTuple.HelpTelephone);
211 } 242 }
212 243
213 if (!String.IsNullOrEmpty(this.BundleInfo.AboutUrl)) 244 if (!String.IsNullOrEmpty(this.BundleTuple.AboutUrl))
214 { 245 {
215 writer.WriteAttributeString("AboutUrl", this.BundleInfo.AboutUrl); 246 writer.WriteAttributeString("AboutUrl", this.BundleTuple.AboutUrl);
216 } 247 }
217 248
218 if (!String.IsNullOrEmpty(this.BundleInfo.UpdateUrl)) 249 if (!String.IsNullOrEmpty(this.BundleTuple.UpdateUrl))
219 { 250 {
220 writer.WriteAttributeString("UpdateUrl", this.BundleInfo.UpdateUrl); 251 writer.WriteAttributeString("UpdateUrl", this.BundleTuple.UpdateUrl);
221 } 252 }
222 253
223 if (!String.IsNullOrEmpty(this.BundleInfo.ParentName)) 254 if (!String.IsNullOrEmpty(this.BundleTuple.ParentName))
224 { 255 {
225 writer.WriteAttributeString("ParentDisplayName", this.BundleInfo.ParentName); 256 writer.WriteAttributeString("ParentDisplayName", this.BundleTuple.ParentName);
226 } 257 }
227 258
228 if (1 == this.BundleInfo.DisableModify) 259 if (this.BundleTuple.DisableModify)
229 { 260 {
230 writer.WriteAttributeString("DisableModify", "yes"); 261 writer.WriteAttributeString("DisableModify", "yes");
231 } 262 }
232 else if (2 == this.BundleInfo.DisableModify) 263
264 if (this.BundleTuple.DisableRemove)
233 { 265 {
234 writer.WriteAttributeString("DisableModify", "button"); 266 writer.WriteAttributeString("DisableRemove", "yes");
235 } 267 }
236 268
237 if (this.BundleInfo.DisableRemove) 269 if (this.BundleTuple.SingleChangeUninstallButton)
238 { 270 {
239 writer.WriteAttributeString("DisableRemove", "yes"); 271 writer.WriteAttributeString("DisableModify", "button");
240 } 272 }
241 writer.WriteEndElement(); // </Arp> 273 writer.WriteEndElement(); // </Arp>
242 274
243 // Get update registration if specified. 275 // Get update registration if specified.
244 WixUpdateRegistrationRow updateRegistrationInfo = this.Output.Tables["WixUpdateRegistration"].RowsAs<WixUpdateRegistrationRow>().FirstOrDefault(); 276 var updateRegistrationInfo = this.Section.Tuples.OfType<WixUpdateRegistrationTuple>().FirstOrDefault();
245 277
246 if (null != updateRegistrationInfo) 278 if (null != updateRegistrationInfo)
247 { 279 {
@@ -263,9 +295,9 @@ namespace WixToolset.Core.Burn.Bundles
263 writer.WriteEndElement(); // </Update> 295 writer.WriteEndElement(); // </Update>
264 } 296 }
265 297
266 IEnumerable<Row> bundleTags = this.Output.Tables["WixBundleTag"].RowsAs<Row>(); 298#if TODO // Handle SWID Tags
267 299 var bundleTags = this.Output.Tables["WixBundleTag"].RowsAs<Row>();
268 foreach (Row row in bundleTags) 300 foreach (var row in bundleTags)
269 { 301 {
270 writer.WriteStartElement("SoftwareTag"); 302 writer.WriteStartElement("SoftwareTag");
271 writer.WriteAttributeString("Filename", (string)row[0]); 303 writer.WriteAttributeString("Filename", (string)row[0]);
@@ -273,6 +305,7 @@ namespace WixToolset.Core.Burn.Bundles
273 writer.WriteCData((string)row[4]); 305 writer.WriteCData((string)row[4]);
274 writer.WriteEndElement(); 306 writer.WriteEndElement();
275 } 307 }
308#endif
276 309
277 writer.WriteEndElement(); // </Register> 310 writer.WriteEndElement(); // </Register>
278 311
@@ -294,25 +327,28 @@ namespace WixToolset.Core.Burn.Bundles
294 } 327 }
295 328
296 // Index a few tables by package. 329 // Index a few tables by package.
297 ILookup<string, WixBundlePatchTargetCodeRow> targetCodesByPatch = this.Output.Tables["WixBundlePatchTargetCode"].RowsAs<WixBundlePatchTargetCodeRow>().ToLookup(r => r.MspPackageId); 330 var targetCodesByPatch = this.Section.Tuples.OfType<WixBundlePatchTargetCodeTuple>().ToLookup(r => r.PackageRef);
298 ILookup<string, WixBundleMsiFeatureRow> msiFeaturesByPackage = this.Output.Tables["WixBundleMsiFeature"].RowsAs<WixBundleMsiFeatureRow>().ToLookup(r => r.ChainPackageId); 331 var msiFeaturesByPackage = this.Section.Tuples.OfType<WixBundleMsiFeatureTuple>().ToLookup(r => r.PackageRef);
299 ILookup<string, WixBundleMsiPropertyRow> msiPropertiesByPackage = this.Output.Tables["WixBundleMsiProperty"].RowsAs<WixBundleMsiPropertyRow>().ToLookup(r => r.ChainPackageId); 332 var msiPropertiesByPackage = this.Section.Tuples.OfType<WixBundleMsiPropertyTuple>().ToLookup(r => r.PackageRef);
300 ILookup<string, WixBundlePayloadRow> payloadsByPackage = this.Payloads.Values.ToLookup(p => p.Package); 333 var payloadsByPackage = this.Payloads.Values.ToLookup(p => p.PackageRef);
301 ILookup<string, WixBundleRelatedPackageRow> relatedPackagesByPackage = this.Output.Tables["WixBundleRelatedPackage"].RowsAs<WixBundleRelatedPackageRow>().ToLookup(r => r.ChainPackageId); 334 var relatedPackagesByPackage = this.Section.Tuples.OfType<WixBundleRelatedPackageTuple>().ToLookup(r => r.PackageRef);
302 ILookup<string, WixBundleSlipstreamMspRow> slipstreamMspsByPackage = this.Output.Tables["WixBundleSlipstreamMsp"].RowsAs<WixBundleSlipstreamMspRow>().ToLookup(r => r.ChainPackageId); 335 var slipstreamMspsByPackage = this.Section.Tuples.OfType<WixBundleSlipstreamMspTuple>().ToLookup(r => r.MspPackageRef);
303 ILookup<string, WixBundlePackageExitCodeRow> exitCodesByPackage = this.Output.Tables["WixBundlePackageExitCode"].RowsAs<WixBundlePackageExitCodeRow>().ToLookup(r => r.ChainPackageId); 336 var exitCodesByPackage = this.Section.Tuples.OfType<WixBundlePackageExitCodeTuple>().ToLookup(r => r.ChainPackageId);
304 ILookup<string, WixBundlePackageCommandLineRow> commandLinesByPackage = this.Output.Tables["WixBundlePackageCommandLine"].RowsAs<WixBundlePackageCommandLineRow>().ToLookup(r => r.ChainPackageId); 337 var commandLinesByPackage = this.Section.Tuples.OfType<WixBundlePackageCommandLineTuple>().ToLookup(r => r.WixBundlePackageRef);
338
339 var dependenciesByPackage = this.Section.Tuples.OfType<ProvidesDependencyTuple>().ToLookup(p => p.PackageRef);
340
305 341
306 // Build up the list of target codes from all the MSPs in the chain. 342 // Build up the list of target codes from all the MSPs in the chain.
307 List<WixBundlePatchTargetCodeRow> targetCodes = new List<WixBundlePatchTargetCodeRow>(); 343 var targetCodes = new List<WixBundlePatchTargetCodeTuple>();
308 344
309 foreach (PackageFacade package in this.OrderedPackages) 345 foreach (var package in this.OrderedPackages)
310 { 346 {
311 writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.Package.Type)); 347 writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.PackageTuple.Type));
312 348
313 writer.WriteAttributeString("Id", package.Package.WixChainItemId); 349 writer.WriteAttributeString("Id", package.PackageId);
314 350
315 switch (package.Package.Cache) 351 switch (package.PackageTuple.Cache)
316 { 352 {
317 case YesNoAlwaysType.No: 353 case YesNoAlwaysType.No:
318 writer.WriteAttributeString("Cache", "no"); 354 writer.WriteAttributeString("Cache", "no");
@@ -325,74 +361,74 @@ namespace WixToolset.Core.Burn.Bundles
325 break; 361 break;
326 } 362 }
327 363
328 writer.WriteAttributeString("CacheId", package.Package.CacheId); 364 writer.WriteAttributeString("CacheId", package.PackageTuple.CacheId);
329 writer.WriteAttributeString("InstallSize", Convert.ToString(package.Package.InstallSize)); 365 writer.WriteAttributeString("InstallSize", Convert.ToString(package.PackageTuple.InstallSize));
330 writer.WriteAttributeString("Size", Convert.ToString(package.Package.Size)); 366 writer.WriteAttributeString("Size", Convert.ToString(package.PackageTuple.Size));
331 writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.Package.PerMachine ? "yes" : "no"); 367 writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.PackageTuple.PerMachine ? "yes" : "no");
332 writer.WriteAttributeString("Permanent", package.Package.Permanent ? "yes" : "no"); 368 writer.WriteAttributeString("Permanent", package.PackageTuple.Permanent ? "yes" : "no");
333 writer.WriteAttributeString("Vital", (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"); 369 writer.WriteAttributeString("Vital", package.PackageTuple.Vital == false ? "no" : "yes");
334 370
335 if (null != package.Package.RollbackBoundary) 371 if (null != package.PackageTuple.RollbackBoundaryRef)
336 { 372 {
337 writer.WriteAttributeString("RollbackBoundaryForward", package.Package.RollbackBoundary); 373 writer.WriteAttributeString("RollbackBoundaryForward", package.PackageTuple.RollbackBoundaryRef);
338 } 374 }
339 375
340 if (!String.IsNullOrEmpty(package.Package.RollbackBoundaryBackward)) 376 if (!String.IsNullOrEmpty(package.PackageTuple.RollbackBoundaryBackwardRef))
341 { 377 {
342 writer.WriteAttributeString("RollbackBoundaryBackward", package.Package.RollbackBoundaryBackward); 378 writer.WriteAttributeString("RollbackBoundaryBackward", package.PackageTuple.RollbackBoundaryBackwardRef);
343 } 379 }
344 380
345 if (!String.IsNullOrEmpty(package.Package.LogPathVariable)) 381 if (!String.IsNullOrEmpty(package.PackageTuple.LogPathVariable))
346 { 382 {
347 writer.WriteAttributeString("LogPathVariable", package.Package.LogPathVariable); 383 writer.WriteAttributeString("LogPathVariable", package.PackageTuple.LogPathVariable);
348 } 384 }
349 385
350 if (!String.IsNullOrEmpty(package.Package.RollbackLogPathVariable)) 386 if (!String.IsNullOrEmpty(package.PackageTuple.RollbackLogPathVariable))
351 { 387 {
352 writer.WriteAttributeString("RollbackLogPathVariable", package.Package.RollbackLogPathVariable); 388 writer.WriteAttributeString("RollbackLogPathVariable", package.PackageTuple.RollbackLogPathVariable);
353 } 389 }
354 390
355 if (!String.IsNullOrEmpty(package.Package.InstallCondition)) 391 if (!String.IsNullOrEmpty(package.PackageTuple.InstallCondition))
356 { 392 {
357 writer.WriteAttributeString("InstallCondition", package.Package.InstallCondition); 393 writer.WriteAttributeString("InstallCondition", package.PackageTuple.InstallCondition);
358 } 394 }
359 395
360 if (WixBundlePackageType.Exe == package.Package.Type) 396 if (package.SpecificPackageTuple is WixBundleExePackageTuple exePackage) // EXE
361 { 397 {
362 writer.WriteAttributeString("DetectCondition", package.ExePackage.DetectCondition); 398 writer.WriteAttributeString("DetectCondition", exePackage.DetectCondition);
363 writer.WriteAttributeString("InstallArguments", package.ExePackage.InstallCommand); 399 writer.WriteAttributeString("InstallArguments", exePackage.InstallCommand);
364 writer.WriteAttributeString("UninstallArguments", package.ExePackage.UninstallCommand); 400 writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand);
365 writer.WriteAttributeString("RepairArguments", package.ExePackage.RepairCommand); 401 writer.WriteAttributeString("RepairArguments", exePackage.RepairCommand);
366 writer.WriteAttributeString("Repairable", package.ExePackage.Repairable ? "yes" : "no"); 402 writer.WriteAttributeString("Repairable", exePackage.Repairable ? "yes" : "no");
367 if (!String.IsNullOrEmpty(package.ExePackage.ExeProtocol)) 403 if (!String.IsNullOrEmpty(exePackage.ExeProtocol))
368 { 404 {
369 writer.WriteAttributeString("Protocol", package.ExePackage.ExeProtocol); 405 writer.WriteAttributeString("Protocol", exePackage.ExeProtocol);
370 } 406 }
371 } 407 }
372 else if (WixBundlePackageType.Msi == package.Package.Type) 408 else if (package.SpecificPackageTuple is WixBundleMsiPackageTuple msiPackage) // MSI
373 { 409 {
374 writer.WriteAttributeString("ProductCode", package.MsiPackage.ProductCode); 410 writer.WriteAttributeString("ProductCode", msiPackage.ProductCode);
375 writer.WriteAttributeString("Language", package.MsiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); 411 writer.WriteAttributeString("Language", msiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture));
376 writer.WriteAttributeString("Version", package.MsiPackage.ProductVersion); 412 writer.WriteAttributeString("Version", msiPackage.ProductVersion);
377 writer.WriteAttributeString("DisplayInternalUI", package.MsiPackage.DisplayInternalUI ? "yes" : "no"); 413 writer.WriteAttributeString("DisplayInternalUI", msiPackage.DisplayInternalUI ? "yes" : "no");
378 if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) 414 if (!String.IsNullOrEmpty(msiPackage.UpgradeCode))
379 { 415 {
380 writer.WriteAttributeString("UpgradeCode", package.MsiPackage.UpgradeCode); 416 writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode);
381 } 417 }
382 } 418 }
383 else if (WixBundlePackageType.Msp == package.Package.Type) 419 else if (package.SpecificPackageTuple is WixBundleMspPackageTuple mspPackage) // MSP
384 { 420 {
385 writer.WriteAttributeString("PatchCode", package.MspPackage.PatchCode); 421 writer.WriteAttributeString("PatchCode", mspPackage.PatchCode);
386 writer.WriteAttributeString("PatchXml", package.MspPackage.PatchXml); 422 writer.WriteAttributeString("PatchXml", mspPackage.PatchXml);
387 writer.WriteAttributeString("DisplayInternalUI", package.MspPackage.DisplayInternalUI ? "yes" : "no"); 423 writer.WriteAttributeString("DisplayInternalUI", mspPackage.DisplayInternalUI ? "yes" : "no");
388 424
389 // If there is still a chance that all of our patches will target a narrow set of 425 // If there is still a chance that all of our patches will target a narrow set of
390 // product codes, add the patch list to the overall list. 426 // product codes, add the patch list to the overall list.
391 if (null != targetCodes) 427 if (null != targetCodes)
392 { 428 {
393 if (!package.MspPackage.TargetUnspecified) 429 if (!mspPackage.TargetUnspecified)
394 { 430 {
395 IEnumerable<WixBundlePatchTargetCodeRow> patchTargetCodes = targetCodesByPatch[package.MspPackage.ChainPackageId]; 431 var patchTargetCodes = targetCodesByPatch[mspPackage.Id.Id];
396 432
397 targetCodes.AddRange(patchTargetCodes); 433 targetCodes.AddRange(patchTargetCodes);
398 } 434 }
@@ -402,24 +438,24 @@ namespace WixToolset.Core.Burn.Bundles
402 } 438 }
403 } 439 }
404 } 440 }
405 else if (WixBundlePackageType.Msu == package.Package.Type) 441 else if (package.SpecificPackageTuple is WixBundleMsuPackageTuple msuPackage) // MSU
406 { 442 {
407 writer.WriteAttributeString("DetectCondition", package.MsuPackage.DetectCondition); 443 writer.WriteAttributeString("DetectCondition", msuPackage.DetectCondition);
408 writer.WriteAttributeString("KB", package.MsuPackage.MsuKB); 444 writer.WriteAttributeString("KB", msuPackage.MsuKB);
409 } 445 }
410 446
411 IEnumerable<WixBundleMsiFeatureRow> packageMsiFeatures = msiFeaturesByPackage[package.Package.WixChainItemId]; 447 var packageMsiFeatures = msiFeaturesByPackage[package.PackageId];
412 448
413 foreach (WixBundleMsiFeatureRow feature in packageMsiFeatures) 449 foreach (var feature in packageMsiFeatures)
414 { 450 {
415 writer.WriteStartElement("MsiFeature"); 451 writer.WriteStartElement("MsiFeature");
416 writer.WriteAttributeString("Id", feature.Name); 452 writer.WriteAttributeString("Id", feature.Name);
417 writer.WriteEndElement(); 453 writer.WriteEndElement();
418 } 454 }
419 455
420 IEnumerable<WixBundleMsiPropertyRow> packageMsiProperties = msiPropertiesByPackage[package.Package.WixChainItemId]; 456 var packageMsiProperties = msiPropertiesByPackage[package.PackageId];
421 457
422 foreach (WixBundleMsiPropertyRow msiProperty in packageMsiProperties) 458 foreach (var msiProperty in packageMsiProperties)
423 { 459 {
424 writer.WriteStartElement("MsiProperty"); 460 writer.WriteStartElement("MsiProperty");
425 writer.WriteAttributeString("Id", msiProperty.Name); 461 writer.WriteAttributeString("Id", msiProperty.Name);
@@ -431,18 +467,18 @@ namespace WixToolset.Core.Burn.Bundles
431 writer.WriteEndElement(); 467 writer.WriteEndElement();
432 } 468 }
433 469
434 IEnumerable<WixBundleSlipstreamMspRow> packageSlipstreamMsps = slipstreamMspsByPackage[package.Package.WixChainItemId]; 470 var packageSlipstreamMsps = slipstreamMspsByPackage[package.PackageId];
435 471
436 foreach (WixBundleSlipstreamMspRow slipstreamMsp in packageSlipstreamMsps) 472 foreach (var slipstreamMsp in packageSlipstreamMsps)
437 { 473 {
438 writer.WriteStartElement("SlipstreamMsp"); 474 writer.WriteStartElement("SlipstreamMsp");
439 writer.WriteAttributeString("Id", slipstreamMsp.MspPackageId); 475 writer.WriteAttributeString("Id", slipstreamMsp.MspPackageRef);
440 writer.WriteEndElement(); 476 writer.WriteEndElement();
441 } 477 }
442 478
443 IEnumerable<WixBundlePackageExitCodeRow> packageExitCodes = exitCodesByPackage[package.Package.WixChainItemId]; 479 var packageExitCodes = exitCodesByPackage[package.PackageId];
444 480
445 foreach (WixBundlePackageExitCodeRow exitCode in packageExitCodes) 481 foreach (var exitCode in packageExitCodes)
446 { 482 {
447 writer.WriteStartElement("ExitCode"); 483 writer.WriteStartElement("ExitCode");
448 484
@@ -459,9 +495,9 @@ namespace WixToolset.Core.Burn.Bundles
459 writer.WriteEndElement(); 495 writer.WriteEndElement();
460 } 496 }
461 497
462 IEnumerable<WixBundlePackageCommandLineRow> packageCommandLines = commandLinesByPackage[package.Package.WixChainItemId]; 498 var packageCommandLines = commandLinesByPackage[package.PackageId];
463 499
464 foreach (WixBundlePackageCommandLineRow commandLine in packageCommandLines) 500 foreach (var commandLine in packageCommandLines)
465 { 501 {
466 writer.WriteStartElement("CommandLine"); 502 writer.WriteStartElement("CommandLine");
467 writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument); 503 writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument);
@@ -472,18 +508,38 @@ namespace WixToolset.Core.Burn.Bundles
472 } 508 }
473 509
474 // Output the dependency information. 510 // Output the dependency information.
475 foreach (ProvidesDependency dependency in package.Provides) 511 var dependencies = dependenciesByPackage[package.PackageId];
512
513 foreach (var dependency in dependencies)
476 { 514 {
477 // TODO: Add to wixpdb as an imported table, or link package wixpdbs to bundle wixpdbs. 515 writer.WriteStartElement("Provides");
478 dependency.WriteXml(writer); 516 writer.WriteAttributeString("Key", dependency.Key);
517
518 if (!String.IsNullOrEmpty(dependency.Version))
519 {
520 writer.WriteAttributeString("Version", dependency.Version);
521 }
522
523 if (!String.IsNullOrEmpty(dependency.DisplayName))
524 {
525 writer.WriteAttributeString("DisplayName", dependency.DisplayName);
526 }
527
528 if (dependency.Imported)
529 {
530 // The package dependency was explicitly authored into the manifest.
531 writer.WriteAttributeString("Imported", "yes");
532 }
533
534 writer.WriteEndElement();
479 } 535 }
480 536
481 IEnumerable<WixBundleRelatedPackageRow> packageRelatedPackages = relatedPackagesByPackage[package.Package.WixChainItemId]; 537 var packageRelatedPackages = relatedPackagesByPackage[package.PackageId];
482 538
483 foreach (WixBundleRelatedPackageRow related in packageRelatedPackages) 539 foreach (var related in packageRelatedPackages)
484 { 540 {
485 writer.WriteStartElement("RelatedPackage"); 541 writer.WriteStartElement("RelatedPackage");
486 writer.WriteAttributeString("Id", related.Id); 542 writer.WriteAttributeString("Id", related.RelatedId);
487 if (!String.IsNullOrEmpty(related.MinVersion)) 543 if (!String.IsNullOrEmpty(related.MinVersion))
488 { 544 {
489 writer.WriteAttributeString("MinVersion", related.MinVersion); 545 writer.WriteAttributeString("MinVersion", related.MinVersion);
@@ -496,7 +552,7 @@ namespace WixToolset.Core.Burn.Bundles
496 } 552 }
497 writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no"); 553 writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no");
498 554
499 string[] relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 555 var relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
500 556
501 if (0 < relatedLanguages.Length) 557 if (0 < relatedLanguages.Length)
502 { 558 {
@@ -513,17 +569,17 @@ namespace WixToolset.Core.Burn.Bundles
513 569
514 // Write any contained Payloads with the PackagePayload being first 570 // Write any contained Payloads with the PackagePayload being first
515 writer.WriteStartElement("PayloadRef"); 571 writer.WriteStartElement("PayloadRef");
516 writer.WriteAttributeString("Id", package.Package.PackagePayload); 572 writer.WriteAttributeString("Id", package.PackageTuple.PayloadRef);
517 writer.WriteEndElement(); 573 writer.WriteEndElement();
518 574
519 IEnumerable<WixBundlePayloadRow> packagePayloads = payloadsByPackage[package.Package.WixChainItemId]; 575 var packagePayloads = payloadsByPackage[package.PackageId];
520 576
521 foreach (WixBundlePayloadRow payload in packagePayloads) 577 foreach (var payload in packagePayloads)
522 { 578 {
523 if (payload.Id != package.Package.PackagePayload) 579 if (payload.Id.Id != package.PackageTuple.PayloadRef)
524 { 580 {
525 writer.WriteStartElement("PayloadRef"); 581 writer.WriteStartElement("PayloadRef");
526 writer.WriteAttributeString("Id", payload.Id); 582 writer.WriteAttributeString("Id", payload.Id.Id);
527 writer.WriteEndElement(); 583 writer.WriteEndElement();
528 } 584 }
529 } 585 }
@@ -534,7 +590,7 @@ namespace WixToolset.Core.Burn.Bundles
534 590
535 if (null != targetCodes) 591 if (null != targetCodes)
536 { 592 {
537 foreach (WixBundlePatchTargetCodeRow targetCode in targetCodes) 593 foreach (var targetCode in targetCodes)
538 { 594 {
539 writer.WriteStartElement("PatchTargetCode"); 595 writer.WriteStartElement("PatchTargetCode");
540 writer.WriteAttributeString("TargetCode", targetCode.TargetCode); 596 writer.WriteAttributeString("TargetCode", targetCode.TargetCode);
@@ -544,12 +600,12 @@ namespace WixToolset.Core.Burn.Bundles
544 } 600 }
545 601
546 // Write the ApprovedExeForElevation elements. 602 // Write the ApprovedExeForElevation elements.
547 IEnumerable<WixApprovedExeForElevationRow> approvedExesForElevation = this.Output.Tables["WixApprovedExeForElevation"].RowsAs<WixApprovedExeForElevationRow>(); 603 var approvedExesForElevation = this.Section.Tuples.OfType<WixApprovedExeForElevationTuple>();
548 604
549 foreach (WixApprovedExeForElevationRow approvedExeForElevation in approvedExesForElevation) 605 foreach (var approvedExeForElevation in approvedExesForElevation)
550 { 606 {
551 writer.WriteStartElement("ApprovedExeForElevation"); 607 writer.WriteStartElement("ApprovedExeForElevation");
552 writer.WriteAttributeString("Id", approvedExeForElevation.Id); 608 writer.WriteAttributeString("Id", approvedExeForElevation.Id.Id);
553 writer.WriteAttributeString("Key", approvedExeForElevation.Key); 609 writer.WriteAttributeString("Key", approvedExeForElevation.Key);
554 610
555 if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName)) 611 if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName))
@@ -569,15 +625,15 @@ namespace WixToolset.Core.Burn.Bundles
569 } 625 }
570 } 626 }
571 627
572 private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerRow container) 628 private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerTuple container)
573 { 629 {
574 writer.WriteAttributeString("Id", container.Id); 630 writer.WriteAttributeString("Id", container.Id.Id);
575 writer.WriteAttributeString("FileSize", container.Size.ToString(CultureInfo.InvariantCulture)); 631 writer.WriteAttributeString("FileSize", container.Size.ToString(CultureInfo.InvariantCulture));
576 writer.WriteAttributeString("Hash", container.Hash); 632 writer.WriteAttributeString("Hash", container.Hash);
577 633
578 if (ContainerType.Detached == container.Type) 634 if (ContainerType.Detached == container.Type)
579 { 635 {
580 string resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id, container.Name); 636 string resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id.Id, container.Name);
581 if (!String.IsNullOrEmpty(resolvedUrl)) 637 if (!String.IsNullOrEmpty(resolvedUrl))
582 { 638 {
583 writer.WriteAttributeString("DownloadUrl", resolvedUrl); 639 writer.WriteAttributeString("DownloadUrl", resolvedUrl);
@@ -593,7 +649,7 @@ namespace WixToolset.Core.Burn.Bundles
593 { 649 {
594 if (!String.IsNullOrEmpty(container.DownloadUrl)) 650 if (!String.IsNullOrEmpty(container.DownloadUrl))
595 { 651 {
596 Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id)); 652 this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id.Id));
597 } 653 }
598 654
599 writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. 655 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
603 } 659 }
604 } 660 }
605 661
606 private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadRow payload, bool embeddedOnly, Dictionary<string, WixBundlePayloadRow> allPayloads) 662 private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadTuple payload, bool embeddedOnly, Dictionary<string, WixBundlePayloadTuple> allPayloads)
607 { 663 {
608 Debug.Assert(!embeddedOnly || PackagingType.Embedded == payload.Packaging); 664 Debug.Assert(!embeddedOnly || PackagingType.Embedded == payload.Packaging);
609 665
610 writer.WriteAttributeString("Id", payload.Id); 666 writer.WriteAttributeString("Id", payload.Id.Id);
611 writer.WriteAttributeString("FilePath", payload.Name); 667 writer.WriteAttributeString("FilePath", payload.Name);
612 writer.WriteAttributeString("FileSize", payload.FileSize.ToString(CultureInfo.InvariantCulture)); 668 writer.WriteAttributeString("FileSize", payload.FileSize.ToString(CultureInfo.InvariantCulture));
613 writer.WriteAttributeString("Hash", payload.Hash); 669 writer.WriteAttributeString("Hash", payload.Hash);
@@ -632,22 +688,22 @@ namespace WixToolset.Core.Burn.Bundles
632 case PackagingType.Embedded: // this means it's in a container. 688 case PackagingType.Embedded: // this means it's in a container.
633 if (!String.IsNullOrEmpty(payload.DownloadUrl)) 689 if (!String.IsNullOrEmpty(payload.DownloadUrl))
634 { 690 {
635 Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id)); 691 this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id.Id));
636 } 692 }
637 693
638 writer.WriteAttributeString("Packaging", "embedded"); 694 writer.WriteAttributeString("Packaging", "embedded");
639 writer.WriteAttributeString("SourcePath", payload.EmbeddedId); 695 writer.WriteAttributeString("SourcePath", payload.EmbeddedId);
640 696
641 if (Compiler.BurnUXContainerId != payload.Container) 697 if (BurnConstants.BurnUXContainerName != payload.ContainerRef)
642 { 698 {
643 writer.WriteAttributeString("Container", payload.Container); 699 writer.WriteAttributeString("Container", payload.ContainerRef);
644 } 700 }
645 break; 701 break;
646 702
647 case PackagingType.External: 703 case PackagingType.External:
648 string packageId = payload.ParentPackagePayload; 704 var packageId = payload.ParentPackagePayloadRef;
649 string parentUrl = payload.ParentPackagePayload == null ? null : allPayloads[payload.ParentPackagePayload].DownloadUrl; 705 var parentUrl = payload.ParentPackagePayloadRef == null ? null : allPayloads[payload.ParentPackagePayloadRef].DownloadUrl;
650 string resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id, payload.Name); 706 var resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id.Id, payload.Name);
651 if (!String.IsNullOrEmpty(resolvedUrl)) 707 if (!String.IsNullOrEmpty(resolvedUrl))
652 { 708 {
653 writer.WriteAttributeString("DownloadUrl", resolvedUrl); 709 writer.WriteAttributeString("DownloadUrl", resolvedUrl);
@@ -662,9 +718,9 @@ namespace WixToolset.Core.Burn.Bundles
662 break; 718 break;
663 } 719 }
664 720
665 if (!String.IsNullOrEmpty(payload.Catalog)) 721 if (!String.IsNullOrEmpty(payload.CatalogRef))
666 { 722 {
667 writer.WriteAttributeString("Catalog", payload.Catalog); 723 writer.WriteAttributeString("Catalog", payload.CatalogRef);
668 } 724 }
669 } 725 }
670 726
@@ -682,6 +738,5 @@ namespace WixToolset.Core.Burn.Bundles
682 738
683 return resolved; 739 return resolved;
684 } 740 }
685#endif
686 } 741 }
687} 742}
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
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO; 7 using System.IO;
9 using System.Linq; 8 using System.Linq;
9 using WixToolset.Core.Native;
10 using WixToolset.Data; 10 using WixToolset.Data;
11 using WixToolset.Data.Tuples;
11 12
12 /// <summary> 13 /// <summary>
13 /// Creates cabinet files. 14 /// Creates cabinet files.
14 /// </summary> 15 /// </summary>
15 internal class CreateContainerCommand 16 internal class CreateContainerCommand
16 { 17 {
17#if TODO 18 public CreateContainerCommand(IEnumerable<WixBundlePayloadTuple> payloads, string outputPath, CompressionLevel? compressionLevel)
18 public CompressionLevel DefaultCompressionLevel { private get; set; } 19 {
20 this.Payloads = payloads;
21 this.OutputPath = outputPath;
22 this.CompressionLevel = compressionLevel;
23 }
24
25 public CreateContainerCommand(string manifestPath, IEnumerable<WixBundlePayloadTuple> payloads, string outputPath, CompressionLevel? compressionLevel)
26 {
27 this.ManifestFile = manifestPath;
28 this.Payloads = payloads;
29 this.OutputPath = outputPath;
30 this.CompressionLevel = compressionLevel;
31 }
32
33 private CompressionLevel? CompressionLevel { get; }
19 34
20 public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; } 35 private string ManifestFile { get; }
21 36
22 public string ManifestFile { private get; set; } 37 private string OutputPath { get; }
23 38
24 public string OutputPath { private get; set; } 39 private IEnumerable<WixBundlePayloadTuple> Payloads { get; }
25 40
26 public string Hash { get; private set; } 41 public string Hash { get; private set; }
27 42
@@ -29,40 +44,34 @@ namespace WixToolset.Core.Burn.Bundles
29 44
30 public void Execute() 45 public void Execute()
31 { 46 {
32 int payloadCount = this.Payloads.Count(); // The number of embedded payloads 47 var payloadCount = this.Payloads.Count(); // The number of embedded payloads
33 48
34 if (!String.IsNullOrEmpty(this.ManifestFile)) 49 if (!String.IsNullOrEmpty(this.ManifestFile))
35 { 50 {
36 ++payloadCount; 51 ++payloadCount;
37 } 52 }
38 53
39 using (var cab = new WixCreateCab(Path.GetFileName(this.OutputPath), Path.GetDirectoryName(this.OutputPath), payloadCount, 0, 0, this.DefaultCompressionLevel)) 54 var cabinetPath = Path.GetFullPath(this.OutputPath);
40 {
41 // If a manifest was provided always add it as "payload 0" to the container.
42 if (!String.IsNullOrEmpty(this.ManifestFile))
43 {
44 cab.AddFile(this.ManifestFile, "0");
45 }
46 55
47 foreach (WixBundlePayloadRow payload in this.Payloads) 56 var files = new List<CabinetCompressFile>();
48 {
49 Debug.Assert(PackagingType.Embedded == payload.Packaging);
50 57
51 Messaging.Instance.OnMessage(WixVerboses.LoadingPayload(payload.FullFileName)); 58 // If a manifest was provided always add it as "payload 0" to the container.
59 if (!String.IsNullOrEmpty(this.ManifestFile))
60 {
61 files.Add(new CabinetCompressFile(this.ManifestFile, "0"));
62 }
52 63
53 cab.AddFile(payload.FullFileName, payload.EmbeddedId); 64 files.AddRange(this.Payloads.Select(p => new CabinetCompressFile(p.SourceFile.Path, p.EmbeddedId)));
54 }
55 65
56 cab.Complete(); 66 var cab = new Cabinet(cabinetPath);
57 } 67 cab.Compress(files, this.CompressionLevel ?? Data.CompressionLevel.Mszip);
58 68
59 // Now that the container is created, set the outputs of the command. 69 // Now that the container is created, set the outputs of the command.
60 FileInfo fileInfo = new FileInfo(this.OutputPath); 70 var fileInfo = new FileInfo(cabinetPath);
61 71
62 this.Hash = Common.GetFileHash(fileInfo.FullName); 72 this.Hash = BundleHashAlgorithm.Hash(fileInfo);
63 73
64 this.Size = fileInfo.Length; 74 this.Size = fileInfo.Length;
65 } 75 }
66#endif
67 } 76 }
68} 77}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Burn;
11 using WixToolset.Data.Tuples;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 internal class CreateNonUXContainers
16 {
17 public CreateNonUXContainers(IBackendHelper backendHelper, IntermediateSection section, WixBootstrapperApplicationTuple bootstrapperApplicationTuple, Dictionary<string, WixBundlePayloadTuple> payloadTuples, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel)
18 {
19 this.BackendHelper = backendHelper;
20 this.Section = section;
21 this.BootstrapperApplicationTuple = bootstrapperApplicationTuple;
22 this.PayloadTuples = payloadTuples;
23 this.IntermediateFolder = intermediateFolder;
24 this.LayoutFolder = layoutFolder;
25 this.DefaultCompressionLevel = defaultCompressionLevel;
26 }
27
28 public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
29
30 public WixBundleContainerTuple UXContainer { get; set; }
31
32 public IEnumerable<WixBundlePayloadTuple> UXContainerPayloads { get; private set; }
33
34 public IEnumerable<WixBundleContainerTuple> Containers { get; private set; }
35
36 private IBackendHelper BackendHelper { get; }
37
38 private IntermediateSection Section { get; }
39
40 private WixBootstrapperApplicationTuple BootstrapperApplicationTuple { get; }
41
42 private Dictionary<string, WixBundlePayloadTuple> PayloadTuples { get; }
43
44 private string IntermediateFolder { get; }
45
46 private string LayoutFolder { get; }
47
48 private CompressionLevel? DefaultCompressionLevel { get; }
49
50 public void Execute()
51 {
52 var fileTransfers = new List<IFileTransfer>();
53
54 var uxPayloadTuples = new List<WixBundlePayloadTuple>();
55
56 var attachedContainerIndex = 1; // count starts at one because UX container is "0".
57
58 var containerTuples = this.Section.Tuples.OfType<WixBundleContainerTuple>().ToList();
59
60 var payloadsByContainer = this.PayloadTuples.Values.ToLookup(p => p.ContainerRef);
61
62 foreach (var container in containerTuples)
63 {
64 var containerId = container.Id.Id;
65
66 var containerPayloads = payloadsByContainer[containerId];
67
68 if (!containerPayloads.Any())
69 {
70 if (containerId != BurnConstants.BurnDefaultAttachedContainerName)
71 {
72 // TODO: display warning that we're ignoring container that ended up with no paylods in it.
73 }
74 }
75 else if (BurnConstants.BurnUXContainerName == containerId)
76 {
77 this.UXContainer = container;
78
79 container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name);
80 container.AttachedContainerIndex = 0;
81
82 // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first
83 // in the list since that is the Payload that Burn attempts to load.
84 var baPayloadId = this.BootstrapperApplicationTuple.Id.Id;
85
86 foreach (var uxPayload in containerPayloads)
87 {
88 if (uxPayload.Id.Id == baPayloadId)
89 {
90 uxPayloadTuples.Insert(0, uxPayload);
91 }
92 else
93 {
94 uxPayloadTuples.Add(uxPayload);
95 }
96 }
97 }
98 else
99 {
100 container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name);
101
102 // Add detached containers to the list of file transfers.
103 if (ContainerType.Detached == container.Type)
104 {
105 var transfer = this.BackendHelper.CreateFileTransfer(container.WorkingPath, Path.Combine(this.LayoutFolder, container.Name), true, container.SourceLineNumbers);
106 fileTransfers.Add(transfer);
107 }
108 else // update the attached container index.
109 {
110 Debug.Assert(ContainerType.Attached == container.Type);
111
112 container.AttachedContainerIndex = attachedContainerIndex;
113 ++attachedContainerIndex;
114 }
115
116 this.CreateContainer(container, containerPayloads, null);
117 }
118 }
119
120 this.Containers = containerTuples;
121 this.UXContainerPayloads = uxPayloadTuples;
122 this.FileTransfers = fileTransfers;
123 }
124
125 private void CreateContainer(WixBundleContainerTuple container, IEnumerable<WixBundlePayloadTuple> containerPayloads, string manifestFile)
126 {
127 var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel);
128 command.Execute();
129
130 container.Hash = command.Hash;
131 container.Size = command.Size;
132 }
133 }
134}
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 @@
3namespace WixToolset.Core.Burn.Bundles 3namespace WixToolset.Core.Burn.Bundles
4{ 4{
5 using System.Collections.Generic; 5 using System.Collections.Generic;
6 using System.Linq;
6 using WixToolset.Data; 7 using WixToolset.Data;
8 using WixToolset.Data.Tuples;
7 9
8 internal class GetPackageFacadesCommand 10 internal class GetPackageFacadesCommand
9 { 11 {
10#if TODO 12 public GetPackageFacadesCommand(IEnumerable<WixBundlePackageTuple> chainPackageTuples, IntermediateSection section)
11 public Table PackageTable { private get; set; } 13 {
12 14 this.ChainPackageTuples = chainPackageTuples;
13 public Table ExePackageTable { private get; set; } 15 this.Section = section;
14 16 }
15 public Table MsiPackageTable { private get; set; }
16 17
17 public Table MspPackageTable { private get; set; } 18 private IEnumerable<WixBundlePackageTuple> ChainPackageTuples { get; }
18 19
19 public Table MsuPackageTable { private get; set; } 20 private IntermediateSection Section { get; }
20 21
21 public IDictionary<string, PackageFacade> PackageFacades { get; private set; } 22 public IDictionary<string, PackageFacade> PackageFacades { get; private set; }
22 23
23 public void Execute() 24 public void Execute()
24 { 25 {
25 RowDictionary<WixBundleExePackageRow> exePackages = new RowDictionary<WixBundleExePackageRow>(this.ExePackageTable); 26 var exePackages = this.Section.Tuples.OfType<WixBundleExePackageTuple>().ToDictionary(t => t.Id.Id);
26 RowDictionary<WixBundleMsiPackageRow> msiPackages = new RowDictionary<WixBundleMsiPackageRow>(this.MsiPackageTable); 27 var msiPackages = this.Section.Tuples.OfType<WixBundleMsiPackageTuple>().ToDictionary(t => t.Id.Id);
27 RowDictionary<WixBundleMspPackageRow> mspPackages = new RowDictionary<WixBundleMspPackageRow>(this.MspPackageTable); 28 var mspPackages = this.Section.Tuples.OfType<WixBundleMspPackageTuple>().ToDictionary(t => t.Id.Id);
28 RowDictionary<WixBundleMsuPackageRow> msuPackages = new RowDictionary<WixBundleMsuPackageRow>(this.MsuPackageTable); 29 var msuPackages = this.Section.Tuples.OfType<WixBundleMsuPackageTuple>().ToDictionary(t => t.Id.Id);
29 30
30 Dictionary<string, PackageFacade> facades = new Dictionary<string, PackageFacade>(this.PackageTable.Rows.Count); 31 var facades = new Dictionary<string, PackageFacade>();
31 32
32 foreach (WixBundlePackageRow package in this.PackageTable.Rows) 33 foreach (var package in this.ChainPackageTuples)
33 { 34 {
34 string id = package.WixChainItemId; 35 var id = package.Id.Id;
35 PackageFacade facade = null;
36
37 switch (package.Type) 36 switch (package.Type)
38 { 37 {
39 case WixBundlePackageType.Exe: 38 case WixBundlePackageType.Exe:
40 facade = new PackageFacade(package, exePackages.Get(id)); 39 if (exePackages.TryGetValue(id, out var exePackage))
41 break; 40 {
42 41 facades.Add(id, new PackageFacade(package, exePackage));
43 case WixBundlePackageType.Msi: 42 }
44 facade = new PackageFacade(package, msiPackages.Get(id)); 43 break;
45 break; 44
46 45 case WixBundlePackageType.Msi:
47 case WixBundlePackageType.Msp: 46 if (msiPackages.TryGetValue(id, out var msiPackage))
48 facade = new PackageFacade(package, mspPackages.Get(id)); 47 {
49 break; 48 facades.Add(id, new PackageFacade(package, msiPackage));
50 49 }
51 case WixBundlePackageType.Msu: 50 break;
52 facade = new PackageFacade(package, msuPackages.Get(id)); 51
53 break; 52 case WixBundlePackageType.Msp:
53 if (mspPackages.TryGetValue(id, out var mspPackage))
54 {
55 facades.Add(id, new PackageFacade(package, mspPackage));
56 }
57 break;
58
59 case WixBundlePackageType.Msu:
60 if (msuPackages.TryGetValue(id, out var msuPackage))
61 {
62 facades.Add(id, new PackageFacade(package, msuPackage));
63 }
64 break;
54 } 65 }
55
56 facades.Add(id, facade);
57 } 66 }
58 67
59 this.PackageFacades = facades; 68 this.PackageFacades = facades;
60 } 69 }
61#endif
62 } 70 }
63} 71}
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
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using WixToolset.Data; 7 using WixToolset.Data;
8 using WixToolset.Data.Tuples;
9 using WixToolset.Extensibility.Services;
8 10
9 internal class OrderPackagesAndRollbackBoundariesCommand 11 internal class OrderPackagesAndRollbackBoundariesCommand
10 { 12 {
11#if TODO 13 public OrderPackagesAndRollbackBoundariesCommand(IMessaging messaging, IEnumerable<WixGroupTuple> groupTuples, Dictionary<string, WixBundleRollbackBoundaryTuple> boundaryTuples, IDictionary<string, PackageFacade> packageFacades)
12 public Table WixGroupTable { private get; set; } 14 {
15 this.Messaging = messaging;
16 this.GroupTuples = groupTuples;
17 this.Boundaries = boundaryTuples;
18 this.PackageFacades = packageFacades;
19 }
20
21 private IMessaging Messaging { get; }
22
23 public IEnumerable<WixGroupTuple> GroupTuples { get; }
13 24
14 public RowDictionary<WixBundleRollbackBoundaryRow> Boundaries { private get; set; } 25 public Dictionary<string, WixBundleRollbackBoundaryTuple> Boundaries { get; }
15 26
16 public IDictionary<string, PackageFacade> PackageFacades { private get; set; } 27 public IDictionary<string, PackageFacade> PackageFacades { get; }
17 28
18 public IEnumerable<PackageFacade> OrderedPackageFacades { get; private set; } 29 public IEnumerable<PackageFacade> OrderedPackageFacades { get; private set; }
19 30
20 public IEnumerable<WixBundleRollbackBoundaryRow> UsedRollbackBoundaries { get; private set; } 31 public IEnumerable<WixBundleRollbackBoundaryTuple> UsedRollbackBoundaries { get; private set; }
21 32
22 public void Execute() 33 public void Execute()
23 { 34 {
24 List<PackageFacade> orderedFacades = new List<PackageFacade>(); 35 var orderedFacades = new List<PackageFacade>();
25 List<WixBundleRollbackBoundaryRow> usedBoundaries = new List<WixBundleRollbackBoundaryRow>(); 36 var usedBoundaries = new List<WixBundleRollbackBoundaryTuple>();
26 37
27 // Process the chain of packages to add them in the correct order 38 // Process the chain of packages to add them in the correct order
28 // and assign the forward rollback boundaries as appropriate. Remember 39 // and assign the forward rollback boundaries as appropriate. Remember
@@ -33,44 +44,44 @@ namespace WixToolset.Core.Burn.Bundles
33 // We handle uninstall (aka: backwards) rollback boundaries after 44 // We handle uninstall (aka: backwards) rollback boundaries after
34 // we get these install/repair (aka: forward) rollback boundaries 45 // we get these install/repair (aka: forward) rollback boundaries
35 // defined. 46 // defined.
36 WixBundleRollbackBoundaryRow previousRollbackBoundary = null; 47 WixBundleRollbackBoundaryTuple previousRollbackBoundary = null;
37 WixBundleRollbackBoundaryRow lastRollbackBoundary = null; 48 WixBundleRollbackBoundaryTuple lastRollbackBoundary = null;
38 bool boundaryHadX86Package = false; 49 var boundaryHadX86Package = false;
39 50
40 foreach (WixGroupRow row in this.WixGroupTable.Rows) 51 foreach (var groupTuple in this.GroupTuples)
41 { 52 {
42 if (ComplexReferenceChildType.Package == row.ChildType && ComplexReferenceParentType.PackageGroup == row.ParentType && "WixChain" == row.ParentId) 53 if (ComplexReferenceChildType.Package == groupTuple.ChildType && ComplexReferenceParentType.PackageGroup == groupTuple.ParentType && "WixChain" == groupTuple.ParentId)
43 { 54 {
44 PackageFacade facade = null; 55 if (this.PackageFacades.TryGetValue(groupTuple.ChildId, out var facade))
45 if (PackageFacades.TryGetValue(row.ChildId, out facade))
46 { 56 {
47 if (null != previousRollbackBoundary) 57 if (null != previousRollbackBoundary)
48 { 58 {
49 usedBoundaries.Add(previousRollbackBoundary); 59 usedBoundaries.Add(previousRollbackBoundary);
50 facade.Package.RollbackBoundary = previousRollbackBoundary.ChainPackageId; 60 facade.PackageTuple.RollbackBoundaryRef = previousRollbackBoundary.Id.Id;
51 previousRollbackBoundary = null; 61 previousRollbackBoundary = null;
52 62
53 boundaryHadX86Package = (facade.Package.x64 == YesNoType.Yes); 63 boundaryHadX86Package = facade.PackageTuple.Win64;
54 } 64 }
55 65
56 // Error if MSI transaction has x86 package preceding x64 packages 66 // Error if MSI transaction has x86 package preceding x64 packages
57 if ((lastRollbackBoundary != null) && (lastRollbackBoundary.Transaction == YesNoType.Yes) 67 if ((lastRollbackBoundary != null)
68 && lastRollbackBoundary.Transaction == true
58 && boundaryHadX86Package 69 && boundaryHadX86Package
59 && (facade.Package.x64 == YesNoType.Yes)) 70 && facade.PackageTuple.Win64)
60 { 71 {
61 Messaging.Instance.OnMessage(WixErrors.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers)); 72 this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers));
62 } 73 }
63 boundaryHadX86Package = boundaryHadX86Package || (facade.Package.x64 == YesNoType.No); 74 boundaryHadX86Package |= facade.PackageTuple.Win64;
64 75
65 orderedFacades.Add(facade); 76 orderedFacades.Add(facade);
66 } 77 }
67 else // must be a rollback boundary. 78 else // must be a rollback boundary.
68 { 79 {
69 // Discard the next rollback boundary if we have a previously defined boundary. 80 // Discard the next rollback boundary if we have a previously defined boundary.
70 WixBundleRollbackBoundaryRow nextRollbackBoundary = Boundaries.Get(row.ChildId); 81 var nextRollbackBoundary = this.Boundaries[groupTuple.ChildId];
71 if (null != previousRollbackBoundary) 82 if (null != previousRollbackBoundary)
72 { 83 {
73 Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.ChainPackageId)); 84 this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id));
74 } 85 }
75 else 86 else
76 { 87 {
@@ -83,7 +94,7 @@ namespace WixToolset.Core.Burn.Bundles
83 94
84 if (null != previousRollbackBoundary) 95 if (null != previousRollbackBoundary)
85 { 96 {
86 Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.ChainPackageId)); 97 this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.Id.Id));
87 } 98 }
88 99
89 // With the forward rollback boundaries assigned, we can now go 100 // With the forward rollback boundaries assigned, we can now go
@@ -120,14 +131,14 @@ namespace WixToolset.Core.Burn.Bundles
120 131
121 foreach (PackageFacade package in orderedFacades) 132 foreach (PackageFacade package in orderedFacades)
122 { 133 {
123 if (null != package.Package.RollbackBoundary) 134 if (null != package.PackageTuple.RollbackBoundaryRef)
124 { 135 {
125 if (null != previousFacade) 136 if (null != previousFacade)
126 { 137 {
127 previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; 138 previousFacade.PackageTuple.RollbackBoundaryBackwardRef = previousRollbackBoundaryId;
128 } 139 }
129 140
130 previousRollbackBoundaryId = package.Package.RollbackBoundary; 141 previousRollbackBoundaryId = package.PackageTuple.RollbackBoundaryRef;
131 } 142 }
132 143
133 previousFacade = package; 144 previousFacade = package;
@@ -135,12 +146,11 @@ namespace WixToolset.Core.Burn.Bundles
135 146
136 if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) 147 if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade)
137 { 148 {
138 previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; 149 previousFacade.PackageTuple.RollbackBoundaryBackwardRef = previousRollbackBoundaryId;
139 } 150 }
140 151
141 this.OrderedPackageFacades = orderedFacades; 152 this.OrderedPackageFacades = orderedFacades;
142 this.UsedRollbackBoundaries = usedBoundaries; 153 this.UsedRollbackBoundaries = usedBoundaries;
143 } 154 }
144#endif
145 } 155 }
146} 156}
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 @@
2 2
3namespace WixToolset.Core.Burn.Bundles 3namespace WixToolset.Core.Burn.Bundles
4{ 4{
5 using System.Diagnostics;
6 using WixToolset.Data;
7 using WixToolset.Data.Tuples;
8
5 internal class PackageFacade 9 internal class PackageFacade
6 { 10 {
7#if TODO 11 public PackageFacade(WixBundlePackageTuple packageTuple, IntermediateTuple specificPackageTuple)
8 private PackageFacade(WixBundlePackageRow package)
9 {
10 this.Package = package;
11 this.Provides = new ProvidesDependencyCollection();
12 }
13
14 public PackageFacade(WixBundlePackageRow package, WixBundleExePackageRow exePackage)
15 : this(package)
16 { 12 {
17 this.ExePackage = exePackage; 13 Debug.Assert(packageTuple.Id.Id == specificPackageTuple.Id.Id);
18 }
19 14
20 public PackageFacade(WixBundlePackageRow package, WixBundleMsiPackageRow msiPackage) 15 this.PackageTuple = packageTuple;
21 : this(package) 16 this.SpecificPackageTuple = specificPackageTuple;
22 {
23 this.MsiPackage = msiPackage;
24 } 17 }
25 18
26 public PackageFacade(WixBundlePackageRow package, WixBundleMspPackageRow mspPackage) 19 public string PackageId => this.PackageTuple.Id.Id;
27 : this(package)
28 {
29 this.MspPackage = mspPackage;
30 }
31
32 public PackageFacade(WixBundlePackageRow package, WixBundleMsuPackageRow msuPackage)
33 : this(package)
34 {
35 this.MsuPackage = msuPackage;
36 }
37
38 public WixBundlePackageRow Package { get; private set; }
39
40 public WixBundleExePackageRow ExePackage { get; private set; }
41
42 public WixBundleMsiPackageRow MsiPackage { get; private set; }
43
44 public WixBundleMspPackageRow MspPackage { get; private set; }
45 20
46 public WixBundleMsuPackageRow MsuPackage { get; private set; } 21 public WixBundlePackageTuple PackageTuple { get; }
47 22
48 /// <summary> 23 public IntermediateTuple SpecificPackageTuple { get; }
49 /// The provides dependencies authored and imported for this package.
50 /// </summary>
51 /// <remarks>
52 /// TODO: Eventually this collection should turn into Rows so they are tracked in the PDB but
53 /// the relationship with the extension makes it much trickier to pull off.
54 /// </remarks>
55 public ProvidesDependencyCollection Provides { get; private set; }
56#endif
57 } 24 }
58} 25}
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 @@
3namespace WixToolset.Core.Burn.Bundles 3namespace WixToolset.Core.Burn.Bundles
4{ 4{
5 using System; 5 using System;
6 using WixToolset.Data; 6 using System.Collections.Generic;
7 using WixToolset.Data.Tuples;
7 8
8 /// <summary> 9 /// <summary>
9 /// Initializes package state from the Exe contents. 10 /// Initializes package state from the Exe contents.
10 /// </summary> 11 /// </summary>
11 internal class ProcessExePackageCommand 12 internal class ProcessExePackageCommand
12 { 13 {
13#if TODO 14 public ProcessExePackageCommand(PackageFacade facade, Dictionary<string, WixBundlePayloadTuple> payloadTuples)
14 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } 15 {
16 this.AuthoredPayloads = payloadTuples;
17 this.Facade = facade;
18 }
19
20 public Dictionary<string, WixBundlePayloadTuple> AuthoredPayloads { get; }
15 21
16 public PackageFacade Facade { private get; set; } 22 public PackageFacade Facade { get; }
17 23
18 /// <summary> 24 /// <summary>
19 /// Processes the Exe packages to add properties and payloads from the Exe packages. 25 /// Processes the Exe packages to add properties and payloads from the Exe packages.
20 /// </summary> 26 /// </summary>
21 public void Execute() 27 public void Execute()
22 { 28 {
23 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); 29 var packagePayload = this.AuthoredPayloads[this.Facade.PackageTuple.PayloadRef];
24 30
25 if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) 31 if (String.IsNullOrEmpty(this.Facade.PackageTuple.CacheId))
26 { 32 {
27 this.Facade.Package.CacheId = packagePayload.Hash; 33 this.Facade.PackageTuple.CacheId = packagePayload.Hash;
28 } 34 }
29 35
30 this.Facade.Package.Version = packagePayload.Version; 36 this.Facade.PackageTuple.Version = packagePayload.Version;
31 } 37 }
32#endif
33 } 38 }
34} 39}
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 @@
3namespace WixToolset.Core.Burn.Bundles 3namespace WixToolset.Core.Burn.Bundles
4{ 4{
5 using System; 5 using System;
6 using System.Collections;
7 using System.Collections.Generic; 6 using System.Collections.Generic;
8 using System.Diagnostics; 7 using System.Diagnostics;
9 using System.Globalization; 8 using System.Globalization;
@@ -11,47 +10,62 @@ namespace WixToolset.Core.Burn.Bundles
11 using System.Linq; 10 using System.Linq;
12 using WixToolset.Data; 11 using WixToolset.Data;
13 using WixToolset.Extensibility; 12 using WixToolset.Extensibility;
14 using WixToolset.Core.Native;
15 using Dtf = WixToolset.Dtf.WindowsInstaller; 13 using Dtf = WixToolset.Dtf.WindowsInstaller;
16 using WixToolset.Data.Bind; 14 using WixToolset.Extensibility.Services;
15 using WixToolset.Data.Tuples;
16 using WixToolset.Data.WindowsInstaller;
17 using WixToolset.Extensibility.Data;
17 18
18 /// <summary> 19 /// <summary>
19 /// Initializes package state from the MSI contents. 20 /// Initializes package state from the MSI contents.
20 /// </summary> 21 /// </summary>
21 internal class ProcessMsiPackageCommand 22 internal class ProcessMsiPackageCommand
22 { 23 {
23#if TODO
24 private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'"; 24 private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'";
25 25
26 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } 26 public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable<IBurnBackendExtension> backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary<string, WixBundlePayloadTuple> payloadTuples)
27 {
28 this.Messaging = serviceProvider.GetService<IMessaging>();
29 this.BackendHelper = serviceProvider.GetService<IBackendHelper>();
30 this.PathResolver = serviceProvider.GetService<IPathResolver>();
31
32 this.BackendExtensions = backendExtensions;
33
34 this.AuthoredPayloads = payloadTuples;
35 this.Section = section;
36 this.Facade = facade;
37 }
38
39 private IMessaging Messaging { get; }
27 40
28 public PackageFacade Facade { private get; set; } 41 private IBackendHelper BackendHelper { get; }
29 42
30 public IEnumerable<IBurnBackendExtension> BackendExtensions { private get; set; } 43 private IPathResolver PathResolver { get; }
31 44
32 public Table MsiFeatureTable { private get; set; } 45 private IEnumerable<IBurnBackendExtension> BackendExtensions { get; }
33 46
34 public Table MsiPropertyTable { private get; set; } 47 private Dictionary<string, WixBundlePayloadTuple> AuthoredPayloads { get; }
35 48
36 public Table PayloadTable { private get; set; } 49 private PackageFacade Facade { get; }
37 50
38 public Table RelatedPackageTable { private get; set; } 51 private IntermediateSection Section { get; }
39 52
40 /// <summary> 53 /// <summary>
41 /// Processes the MSI packages to add properties and payloads from the MSI packages. 54 /// Processes the MSI packages to add properties and payloads from the MSI packages.
42 /// </summary> 55 /// </summary>
43 public void Execute() 56 public void Execute()
44 { 57 {
45 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); 58 var packagePayload = this.AuthoredPayloads[this.Facade.PackageTuple.PayloadRef];
46 59
47 string sourcePath = packagePayload.FullFileName; 60 var msiPackage = (WixBundleMsiPackageTuple)this.Facade.SpecificPackageTuple;
48 bool longNamesInImage = false; 61
49 bool compressed = false; 62 var sourcePath = packagePayload.SourceFile.Path;
50 bool x64 = false; 63 var longNamesInImage = false;
64 var compressed = false;
51 try 65 try
52 { 66 {
53 // Read data out of the msi database... 67 // Read data out of the msi database...
54 using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) 68 using (var sumInfo = new Dtf.SummaryInfo(sourcePath, false))
55 { 69 {
56 // 1 is the Word Count summary information stream bit that means 70 // 1 is the Word Count summary information stream bit that means
57 // the MSI uses short file names when set. We care about long file 71 // the MSI uses short file names when set. We care about long file
@@ -62,83 +76,84 @@ namespace WixToolset.Core.Burn.Bundles
62 // files are compressed in the MSI by default when the bit is set. 76 // files are compressed in the MSI by default when the bit is set.
63 compressed = 2 == (sumInfo.WordCount & 2); 77 compressed = 2 == (sumInfo.WordCount & 2);
64 78
65 x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64"));
66
67 // 8 is the Word Count summary information stream bit that means 79 // 8 is the Word Count summary information stream bit that means
68 // "Elevated privileges are not required to install this package." 80 // "Elevated privileges are not required to install this package."
69 // in MSI 4.5 and below, if this bit is 0, elevation is required. 81 // in MSI 4.5 and below, if this bit is 0, elevation is required.
70 this.Facade.Package.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No; 82 var perMachine = (0 == (sumInfo.WordCount & 8));
71 this.Facade.Package.x64 = x64 ? YesNoType.Yes : YesNoType.No; 83 var x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64"));
84
85 this.Facade.PackageTuple.PerMachine = perMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No;
86 this.Facade.PackageTuple.Win64 = x64;
72 } 87 }
73 88
74 using (Dtf.Database db = new Dtf.Database(sourcePath)) 89 using (var db = new Dtf.Database(sourcePath))
75 { 90 {
76 this.Facade.MsiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode"); 91 msiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode");
77 this.Facade.MsiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode"); 92 msiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode");
78 this.Facade.MsiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer"); 93 msiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer");
79 this.Facade.MsiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture); 94 msiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture);
80 this.Facade.MsiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion"); 95 msiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion");
81 96
82 if (!Common.IsValidModuleOrBundleVersion(this.Facade.MsiPackage.ProductVersion)) 97 if (!Common.IsValidModuleOrBundleVersion(msiPackage.ProductVersion))
83 { 98 {
84 // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? 99 // not a proper .NET version (e.g., five fields); can we get a valid four-part version number?
85 string version = null; 100 string version = null;
86 string[] versionParts = this.Facade.MsiPackage.ProductVersion.Split('.'); 101 string[] versionParts = msiPackage.ProductVersion.Split('.');
87 int count = versionParts.Length; 102 var count = versionParts.Length;
88 if (0 < count) 103 if (0 < count)
89 { 104 {
90 version = versionParts[0]; 105 version = versionParts[0];
91 for (int i = 1; i < 4 && i < count; ++i) 106 for (var i = 1; i < 4 && i < count; ++i)
92 { 107 {
93 version = String.Concat(version, ".", versionParts[i]); 108 version = String.Concat(version, ".", versionParts[i]);
94 } 109 }
95 } 110 }
96 111
97 if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version)) 112 if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version))
98 { 113 {
99 Messaging.Instance.OnMessage(WixWarnings.VersionTruncated(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath, version)); 114 this.Messaging.Write(WarningMessages.VersionTruncated(this.Facade.PackageTuple.SourceLineNumbers, msiPackage.ProductVersion, sourcePath, version));
100 this.Facade.MsiPackage.ProductVersion = version; 115 msiPackage.ProductVersion = version;
101 } 116 }
102 else 117 else
103 { 118 {
104 Messaging.Instance.OnMessage(WixErrors.InvalidProductVersion(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath)); 119 this.Messaging.Write(ErrorMessages.InvalidProductVersion(this.Facade.PackageTuple.SourceLineNumbers, msiPackage.ProductVersion, sourcePath));
105 } 120 }
106 } 121 }
107 122
108 if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) 123 if (String.IsNullOrEmpty(this.Facade.PackageTuple.CacheId))
109 { 124 {
110 this.Facade.Package.CacheId = String.Format("{0}v{1}", this.Facade.MsiPackage.ProductCode, this.Facade.MsiPackage.ProductVersion); 125 this.Facade.PackageTuple.CacheId = String.Format("{0}v{1}", msiPackage.ProductCode, msiPackage.ProductVersion);
111 } 126 }
112 127
113 if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) 128 if (String.IsNullOrEmpty(this.Facade.PackageTuple.DisplayName))
114 { 129 {
115 this.Facade.Package.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName"); 130 this.Facade.PackageTuple.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName");
116 } 131 }
117 132
118 if (String.IsNullOrEmpty(this.Facade.Package.Description)) 133 if (String.IsNullOrEmpty(this.Facade.PackageTuple.Description))
119 { 134 {
120 this.Facade.Package.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS"); 135 this.Facade.PackageTuple.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS");
121 } 136 }
122 137
123 ISet<string> payloadNames = this.GetPayloadTargetNames(); 138 var payloadNames = this.GetPayloadTargetNames(packagePayload.Id.Id);
124 139
125 ISet<string> msiPropertyNames = this.GetMsiPropertyNames(); 140 var msiPropertyNames = this.GetMsiPropertyNames(packagePayload.Id.Id);
126 141
127 this.SetPerMachineAppropriately(db, sourcePath); 142 this.SetPerMachineAppropriately(db, msiPackage, sourcePath);
128 143
129 // Ensure the MSI package is appropriately marked visible or not. 144 // Ensure the MSI package is appropriately marked visible or not.
130 this.SetPackageVisibility(db, msiPropertyNames); 145 this.SetPackageVisibility(db, msiPackage, msiPropertyNames);
131 146
132 // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. 147 // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance.
133 if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL")) 148 if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL"))
134 { 149 {
135 this.AddMsiProperty("MSIFASTINSTALL", "7"); 150 this.AddMsiProperty(msiPackage, "MSIFASTINSTALL", "7");
136 } 151 }
137 152
138 this.CreateRelatedPackages(db); 153 this.CreateRelatedPackages(db);
139 154
140 // If feature selection is enabled, represent the Feature table in the manifest. 155 // If feature selection is enabled, represent the Feature table in the manifest.
141 if (this.Facade.MsiPackage.EnableFeatureSelection) 156 if ((msiPackage.Attributes & WixBundleMsiPackageAttributes.EnableFeatureSelection) == WixBundleMsiPackageAttributes.EnableFeatureSelection)
142 { 157 {
143 this.CreateMsiFeatures(db); 158 this.CreateMsiFeatures(db);
144 } 159 }
@@ -148,90 +163,92 @@ namespace WixToolset.Core.Burn.Bundles
148 163
149 // Add all external files as package payloads and calculate the total install size as the rollup of 164 // Add all external files as package payloads and calculate the total install size as the rollup of
150 // File table's sizes. 165 // File table's sizes.
151 this.Facade.Package.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); 166 this.Facade.PackageTuple.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames);
152 167
153 // Add all dependency providers from the MSI. 168 // Add all dependency providers from the MSI.
154 this.ImportDependencyProviders(db); 169 this.ImportDependencyProviders(msiPackage, db);
155 } 170 }
156 } 171 }
157 catch (Dtf.InstallerException e) 172 catch (Dtf.InstallerException e)
158 { 173 {
159 Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(this.Facade.Package.SourceLineNumbers, sourcePath, e.Message)); 174 this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.Facade.PackageTuple.SourceLineNumbers, sourcePath, e.Message));
160 } 175 }
161 } 176 }
162 177
163 private ISet<string> GetPayloadTargetNames() 178 private ISet<string> GetPayloadTargetNames(string packageId)
164 { 179 {
165 IEnumerable<string> payloadNames = this.PayloadTable.RowsAs<WixBundlePayloadRow>() 180 var payloadNames = this.Section.Tuples.OfType<WixBundlePayloadTuple>()
166 .Where(r => r.Package == this.Facade.Package.WixChainItemId) 181 .Where(p => p.PackageRef == packageId)
167 .Select(r => r.Name); 182 .Select(p => p.Name);
168 183
169 return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase); 184 return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase);
170 } 185 }
171 186
172 private ISet<string> GetMsiPropertyNames() 187 private ISet<string> GetMsiPropertyNames(string packageId)
173 { 188 {
174 IEnumerable<string> properties = this.MsiPropertyTable.RowsAs<WixBundleMsiPropertyRow>() 189 var properties = this.Section.Tuples.OfType<WixBundleMsiPropertyTuple>()
175 .Where(r => r.ChainPackageId == this.Facade.Package.WixChainItemId) 190 .Where(p => p.Id.Id == packageId)
176 .Select(r => r.Name); 191 .Select(p => p.Name);
177 192
178 return new HashSet<string>(properties, StringComparer.Ordinal); 193 return new HashSet<string>(properties, StringComparer.Ordinal);
179 } 194 }
180 195
181 private void SetPerMachineAppropriately(Dtf.Database db, string sourcePath) 196 private void SetPerMachineAppropriately(Dtf.Database db, WixBundleMsiPackageTuple msiPackage, string sourcePath)
182 { 197 {
183 if (this.Facade.MsiPackage.ForcePerMachine) 198 if (msiPackage.ForcePerMachine)
184 { 199 {
185 if (YesNoDefaultType.No == this.Facade.Package.PerMachine) 200 if (YesNoDefaultType.No == this.Facade.PackageTuple.PerMachine)
186 { 201 {
187 Messaging.Instance.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.Facade.Package.SourceLineNumbers, sourcePath)); 202 this.Messaging.Write(WarningMessages.PerUserButForcingPerMachine(this.Facade.PackageTuple.SourceLineNumbers, sourcePath));
188 this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. 203 this.Facade.PackageTuple.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine.
189 } 204 }
190 205
191 // Force ALLUSERS=1 via the MSI command-line. 206 // Force ALLUSERS=1 via the MSI command-line.
192 this.AddMsiProperty("ALLUSERS", "1"); 207 this.AddMsiProperty(msiPackage, "ALLUSERS", "1");
193 } 208 }
194 else 209 else
195 { 210 {
196 string allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS"); 211 var allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS");
197 212
198 if (String.IsNullOrEmpty(allusers)) 213 if (String.IsNullOrEmpty(allusers))
199 { 214 {
200 // Not forced per-machine and no ALLUSERS property, flip back to per-user. 215 // Not forced per-machine and no ALLUSERS property, flip back to per-user.
201 if (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) 216 if (YesNoDefaultType.Yes == this.Facade.PackageTuple.PerMachine)
202 { 217 {
203 Messaging.Instance.OnMessage(WixWarnings.ImplicitlyPerUser(this.Facade.Package.SourceLineNumbers, sourcePath)); 218 this.Messaging.Write(WarningMessages.ImplicitlyPerUser(this.Facade.PackageTuple.SourceLineNumbers, sourcePath));
204 this.Facade.Package.PerMachine = YesNoDefaultType.No; 219 this.Facade.PackageTuple.PerMachine = YesNoDefaultType.No;
205 } 220 }
206 } 221 }
207 else if (allusers.Equals("1", StringComparison.Ordinal)) 222 else if (allusers.Equals("1", StringComparison.Ordinal))
208 { 223 {
209 if (YesNoDefaultType.No == this.Facade.Package.PerMachine) 224 if (YesNoDefaultType.No == this.Facade.PackageTuple.PerMachine)
210 { 225 {
211 Messaging.Instance.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.Facade.Package.SourceLineNumbers, sourcePath)); 226 this.Messaging.Write(ErrorMessages.PerUserButAllUsersEquals1(this.Facade.PackageTuple.SourceLineNumbers, sourcePath));
212 } 227 }
213 } 228 }
214 else if (allusers.Equals("2", StringComparison.Ordinal)) 229 else if (allusers.Equals("2", StringComparison.Ordinal))
215 { 230 {
216 Messaging.Instance.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) ? "machine" : "user")); 231 this.Messaging.Write(WarningMessages.DiscouragedAllUsersValue(this.Facade.PackageTuple.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.PackageTuple.PerMachine) ? "machine" : "user"));
217 } 232 }
218 else 233 else
219 { 234 {
220 Messaging.Instance.OnMessage(WixErrors.UnsupportedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, allusers)); 235 this.Messaging.Write(ErrorMessages.UnsupportedAllUsersValue(this.Facade.PackageTuple.SourceLineNumbers, sourcePath, allusers));
221 } 236 }
222 } 237 }
223 } 238 }
224 239
225 private void SetPackageVisibility(Dtf.Database db, ISet<string> msiPropertyNames) 240 private void SetPackageVisibility(Dtf.Database db, WixBundleMsiPackageTuple msiPackage, ISet<string> msiPropertyNames)
226 { 241 {
227 bool alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT"); 242 var alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT");
243 var visible = (this.Facade.PackageTuple.Attributes & WixBundlePackageAttributes.Visible) == WixBundlePackageAttributes.Visible;
228 244
229 if (alreadyVisible != this.Facade.Package.Visible) // if not already set to the correct visibility. 245 // If not already set to the correct visibility.
246 if (alreadyVisible != visible)
230 { 247 {
231 // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. 248 // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again.
232 if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) 249 if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT"))
233 { 250 {
234 this.AddMsiProperty("ARPSYSTEMCOMPONENT", this.Facade.Package.Visible ? String.Empty : "1"); 251 this.AddMsiProperty(msiPackage, "ARPSYSTEMCOMPONENT", visible ? String.Empty : "1");
235 } 252 }
236 } 253 }
237 } 254 }
@@ -241,30 +258,35 @@ namespace WixToolset.Core.Burn.Bundles
241 // Represent the Upgrade table as related packages. 258 // Represent the Upgrade table as related packages.
242 if (db.Tables.Contains("Upgrade")) 259 if (db.Tables.Contains("Upgrade"))
243 { 260 {
244 using (Dtf.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) 261 using (var view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`"))
245 { 262 {
246 view.Execute(); 263 view.Execute();
247 while (true) 264 while (true)
248 { 265 {
249 using (Dtf.Record record = view.Fetch()) 266 using (var record = view.Fetch())
250 { 267 {
251 if (null == record) 268 if (null == record)
252 { 269 {
253 break; 270 break;
254 } 271 }
255 272
256 WixBundleRelatedPackageRow related = (WixBundleRelatedPackageRow)this.RelatedPackageTable.CreateRow(this.Facade.Package.SourceLineNumbers); 273 var recordAttributes = record.GetInteger(5);
257 related.ChainPackageId = this.Facade.Package.WixChainItemId; 274
258 related.Id = record.GetString(1); 275 var attributes = WixBundleRelatedPackageAttributes.None;
259 related.MinVersion = record.GetString(2); 276 attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect ? WixBundleRelatedPackageAttributes.OnlyDetect : 0;
260 related.MaxVersion = record.GetString(3); 277 attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive ? WixBundleRelatedPackageAttributes.MinInclusive : 0;
261 related.Languages = record.GetString(4); 278 attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive ? WixBundleRelatedPackageAttributes.MaxInclusive : 0;
262 279 attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive ? WixBundleRelatedPackageAttributes.LangInclusive : 0;
263 int attributes = record.GetInteger(5); 280
264 related.OnlyDetect = (attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect; 281 var related = new WixBundleRelatedPackageTuple(this.Facade.PackageTuple.SourceLineNumbers)
265 related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive; 282 {
266 related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive; 283 PackageRef = this.Facade.PackageId,
267 related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0; 284 RelatedId = record.GetString(1),
285 MinVersion = record.GetString(2),
286 MaxVersion = record.GetString(3),
287 Languages = record.GetString(4),
288 Attributes = attributes,
289 };
268 } 290 }
269 } 291 }
270 } 292 }
@@ -275,26 +297,26 @@ namespace WixToolset.Core.Burn.Bundles
275 { 297 {
276 if (db.Tables.Contains("Feature")) 298 if (db.Tables.Contains("Feature"))
277 { 299 {
278 using (Dtf.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) 300 using (var featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?"))
279 using (Dtf.View componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) 301 using (var componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?"))
280 { 302 {
281 using (Dtf.Record featureRecord = new Dtf.Record(1)) 303 using (var featureRecord = new Dtf.Record(1))
282 using (Dtf.Record componentRecord = new Dtf.Record(1)) 304 using (var componentRecord = new Dtf.Record(1))
283 { 305 {
284 using (Dtf.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`")) 306 using (var allFeaturesView = db.OpenView("SELECT * FROM `Feature`"))
285 { 307 {
286 allFeaturesView.Execute(); 308 allFeaturesView.Execute();
287 309
288 while (true) 310 while (true)
289 { 311 {
290 using (Dtf.Record allFeaturesResultRecord = allFeaturesView.Fetch()) 312 using (var allFeaturesResultRecord = allFeaturesView.Fetch())
291 { 313 {
292 if (null == allFeaturesResultRecord) 314 if (null == allFeaturesResultRecord)
293 { 315 {
294 break; 316 break;
295 } 317 }
296 318
297 string featureName = allFeaturesResultRecord.GetString(1); 319 var featureName = allFeaturesResultRecord.GetString(1);
298 320
299 // Calculate the Feature size. 321 // Calculate the Feature size.
300 featureRecord.SetString(1, featureName); 322 featureRecord.SetString(1, featureName);
@@ -304,43 +326,46 @@ namespace WixToolset.Core.Burn.Bundles
304 long size = 0; 326 long size = 0;
305 while (true) 327 while (true)
306 { 328 {
307 using (Dtf.Record componentResultRecord = featureView.Fetch()) 329 using (var componentResultRecord = featureView.Fetch())
308 { 330 {
309 if (null == componentResultRecord) 331 if (null == componentResultRecord)
310 { 332 {
311 break; 333 break;
312 } 334 }
313 string component = componentResultRecord.GetString(1); 335
336 var component = componentResultRecord.GetString(1);
314 componentRecord.SetString(1, component); 337 componentRecord.SetString(1, component);
315 componentView.Execute(componentRecord); 338 componentView.Execute(componentRecord);
316 339
317 while (true) 340 while (true)
318 { 341 {
319 using (Dtf.Record fileResultRecord = componentView.Fetch()) 342 using (var fileResultRecord = componentView.Fetch())
320 { 343 {
321 if (null == fileResultRecord) 344 if (null == fileResultRecord)
322 { 345 {
323 break; 346 break;
324 } 347 }
325 348
326 string fileSize = fileResultRecord.GetString(1); 349 var fileSize = fileResultRecord.GetString(1);
327 size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); 350 size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat);
328 } 351 }
329 } 352 }
330 } 353 }
331 } 354 }
332 355
333 WixBundleMsiFeatureRow feature = (WixBundleMsiFeatureRow)this.MsiFeatureTable.CreateRow(this.Facade.Package.SourceLineNumbers); 356 var feature = new WixBundleMsiFeatureTuple(this.Facade.PackageTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, this.Facade.PackageId, featureName))
334 feature.ChainPackageId = this.Facade.Package.WixChainItemId; 357 {
335 feature.Name = featureName; 358 PackageRef = this.Facade.PackageId,
336 feature.Parent = allFeaturesResultRecord.GetString(2); 359 Name = featureName,
337 feature.Title = allFeaturesResultRecord.GetString(3); 360 Parent = allFeaturesResultRecord.GetString(2),
338 feature.Description = allFeaturesResultRecord.GetString(4); 361 Title = allFeaturesResultRecord.GetString(3),
339 feature.Display = allFeaturesResultRecord.GetInteger(5); 362 Description = allFeaturesResultRecord.GetString(4),
340 feature.Level = allFeaturesResultRecord.GetInteger(6); 363 Display = allFeaturesResultRecord.GetInteger(5),
341 feature.Directory = allFeaturesResultRecord.GetString(7); 364 Level = allFeaturesResultRecord.GetInteger(6),
342 feature.Attributes = allFeaturesResultRecord.GetInteger(8); 365 Directory = allFeaturesResultRecord.GetString(7),
343 feature.Size = size; 366 Attributes = allFeaturesResultRecord.GetInteger(8),
367 Size = size
368 };
344 } 369 }
345 } 370 }
346 } 371 }
@@ -349,113 +374,119 @@ namespace WixToolset.Core.Burn.Bundles
349 } 374 }
350 } 375 }
351 376
352 private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadRow packagePayload, ISet<string> payloadNames) 377 private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadTuple packagePayload, ISet<string> payloadNames)
353 { 378 {
354 if (db.Tables.Contains("Media")) 379 if (db.Tables.Contains("Media"))
355 { 380 {
356 foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`")) 381 foreach (var cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`"))
357 { 382 {
358 if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) 383 if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal))
359 { 384 {
360 // If we didn't find the Payload as an existing child of the package, we need to 385 // If we didn't find the Payload as an existing child of the package, we need to
361 // add it. We expect the file to exist on-disk in the same relative location as 386 // add it. We expect the file to exist on-disk in the same relative location as
362 // the MSI expects to find it... 387 // the MSI expects to find it...
363 string cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); 388 var cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet);
364 389
365 if (!payloadNames.Contains(cabinetName)) 390 if (!payloadNames.Contains(cabinetName))
366 { 391 {
367 string generatedId = Common.GenerateIdentifier("cab", packagePayload.Id, cabinet); 392 var generatedId = Common.GenerateIdentifier("cab", packagePayload.Id.Id, cabinet);
368 string payloadSourceFile = this.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.Package.SourceLineNumbers, BindStage.Normal); 393 var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.PackageTuple.SourceLineNumbers, BindStage.Normal);
369 394
370 WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); 395 var tuple = new WixBundlePayloadTuple(this.Facade.PackageTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId))
371 payload.Id = generatedId; 396 {
372 payload.Name = cabinetName; 397 Name = cabinetName,
373 payload.SourceFile = payloadSourceFile; 398 SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile },
374 payload.Compressed = packagePayload.Compressed; 399 Compressed = packagePayload.Compressed,
375 payload.UnresolvedSourceFile = cabinetName; 400 UnresolvedSourceFile = cabinetName,
376 payload.Package = packagePayload.Package; 401 PackageRef = packagePayload.PackageRef,
377 payload.Container = packagePayload.Container; 402 ContainerRef = packagePayload.ContainerRef,
378 payload.ContentFile = true; 403 ContentFile = true,
379 payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; 404 EnableSignatureValidation = packagePayload.EnableSignatureValidation,
380 payload.Packaging = packagePayload.Packaging; 405 Packaging = packagePayload.Packaging,
381 payload.ParentPackagePayload = packagePayload.Id; 406 ParentPackagePayloadRef = packagePayload.Id.Id,
407 };
408
409 this.Section.Tuples.Add(tuple);
382 } 410 }
383 } 411 }
384 } 412 }
385 } 413 }
386 } 414 }
387 415
388 private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadRow packagePayload, bool longNamesInImage, bool compressed, ISet<string> payloadNames) 416 private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadTuple packagePayload, bool longNamesInImage, bool compressed, ISet<string> payloadNames)
389 { 417 {
390 long size = 0; 418 long size = 0;
391 419
392 if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File")) 420 if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File"))
393 { 421 {
394 Hashtable directories = new Hashtable(); 422 var directories = new Dictionary<string, IResolvedDirectory>();
395 423
396 // Load up the directory hash table so we will be able to resolve source paths 424 // Load up the directory hash table so we will be able to resolve source paths
397 // for files in the MSI database. 425 // for files in the MSI database.
398 using (Dtf.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) 426 using (var view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
399 { 427 {
400 view.Execute(); 428 view.Execute();
401 while (true) 429 while (true)
402 { 430 {
403 using (Dtf.Record record = view.Fetch()) 431 using (var record = view.Fetch())
404 { 432 {
405 if (null == record) 433 if (null == record)
406 { 434 {
407 break; 435 break;
408 } 436 }
409 437
410 string sourceName = Common.GetName(record.GetString(3), true, longNamesInImage); 438 var sourceName = Common.GetName(record.GetString(3), true, longNamesInImage);
411 directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName)); 439
440 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(record.GetString(2), sourceName);
441
442 directories.Add(record.GetString(1), resolvedDirectory);
412 } 443 }
413 } 444 }
414 } 445 }
415 446
416 // Resolve the source paths to external files and add each file size to the total 447 // Resolve the source paths to external files and add each file size to the total
417 // install size of the package. 448 // install size of the package.
418 using (Dtf.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) 449 using (var view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`"))
419 { 450 {
420 view.Execute(); 451 view.Execute();
421 while (true) 452 while (true)
422 { 453 {
423 using (Dtf.Record record = view.Fetch()) 454 using (var record = view.Fetch())
424 { 455 {
425 if (null == record) 456 if (null == record)
426 { 457 {
427 break; 458 break;
428 } 459 }
429 460
430 // Skip adding the loose files as payloads if it was suppressed. 461 // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not
431 if (!this.Facade.MsiPackage.SuppressLooseFilePayloadGeneration) 462 // explicitly marked compressed then this is an external file.
463 var compressionBit = record.GetInteger(4);
464 if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) ||
465 (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed)))
432 { 466 {
433 // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not 467 string fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage);
434 // explicitly marked compressed then this is an external file. 468 var name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath);
435 if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) || 469
436 (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed))) 470 if (!payloadNames.Contains(name))
437 { 471 {
438 string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); 472 var generatedId = Common.GenerateIdentifier("f", packagePayload.Id.Id, record.GetString(2));
439 string name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); 473 var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.PackageTuple.SourceLineNumbers, BindStage.Normal);
440 474
441 if (!payloadNames.Contains(name)) 475 var tuple = new WixBundlePayloadTuple(this.Facade.PackageTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId))
442 { 476 {
443 string generatedId = Common.GenerateIdentifier("f", packagePayload.Id, record.GetString(2)); 477 Name = name,
444 string payloadSourceFile = this.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.Package.SourceLineNumbers, BindStage.Normal); 478 SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile },
445 479 Compressed = packagePayload.Compressed,
446 WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); 480 UnresolvedSourceFile = name,
447 payload.Id = generatedId; 481 PackageRef = packagePayload.PackageRef,
448 payload.Name = name; 482 ContainerRef = packagePayload.ContainerRef,
449 payload.SourceFile = payloadSourceFile; 483 ContentFile = true,
450 payload.Compressed = packagePayload.Compressed; 484 EnableSignatureValidation = packagePayload.EnableSignatureValidation,
451 payload.UnresolvedSourceFile = name; 485 Packaging = packagePayload.Packaging,
452 payload.Package = packagePayload.Package; 486 ParentPackagePayloadRef = packagePayload.Id.Id,
453 payload.Container = packagePayload.Container; 487 };
454 payload.ContentFile = true; 488
455 payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; 489 this.Section.Tuples.Add(tuple);
456 payload.Packaging = packagePayload.Packaging;
457 payload.ParentPackagePayload = packagePayload.Id;
458 }
459 } 490 }
460 } 491 }
461 492
@@ -468,26 +499,30 @@ namespace WixToolset.Core.Burn.Bundles
468 return size; 499 return size;
469 } 500 }
470 501
471 private void AddMsiProperty(string name, string value) 502 private void AddMsiProperty(WixBundleMsiPackageTuple msiPackage, string name, string value)
472 { 503 {
473 WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.MsiPropertyTable.CreateRow(this.Facade.MsiPackage.SourceLineNumbers); 504 var tuple = new WixBundleMsiPropertyTuple(msiPackage.SourceLineNumbers, new Identifier(AccessModifier.Private, msiPackage.Id.Id, name))
474 row.ChainPackageId = this.Facade.Package.WixChainItemId; 505 {
475 row.Name = name; 506 PackageRef = msiPackage.Id.Id,
476 row.Value = value; 507 Name = name,
508 Value = value
509 };
510
511 this.Section.Tuples.Add(tuple);
477 } 512 }
478 513
479 private void ImportDependencyProviders(Dtf.Database db) 514 private void ImportDependencyProviders(WixBundleMsiPackageTuple msiPackage, Dtf.Database db)
480 { 515 {
481 if (db.Tables.Contains("WixDependencyProvider")) 516 if (db.Tables.Contains("WixDependencyProvider"))
482 { 517 {
483 string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; 518 var query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`";
484 519
485 using (Dtf.View view = db.OpenView(query)) 520 using (var view = db.OpenView(query))
486 { 521 {
487 view.Execute(); 522 view.Execute();
488 while (true) 523 while (true)
489 { 524 {
490 using (Dtf.Record record = view.Fetch()) 525 using (var record = view.Fetch())
491 { 526 {
492 if (null == record) 527 if (null == record)
493 { 528 {
@@ -495,34 +530,51 @@ namespace WixToolset.Core.Burn.Bundles
495 } 530 }
496 531
497 // Import the provider key and attributes. 532 // Import the provider key and attributes.
498 string providerKey = record.GetString(1); 533 var tuple = new ProvidesDependencyTuple(msiPackage.SourceLineNumbers)
499 string version = record.GetString(2) ?? this.Facade.MsiPackage.ProductVersion; 534 {
500 string displayName = record.GetString(3) ?? this.Facade.Package.DisplayName; 535 PackageRef = msiPackage.Id.Id,
501 int attributes = record.GetInteger(4); 536 Key = record.GetString(1),
502 537 Version = record.GetString(2) ?? msiPackage.ProductVersion,
503 ProvidesDependency dependency = new ProvidesDependency(providerKey, version, displayName, attributes); 538 DisplayName = record.GetString(3) ?? this.Facade.PackageTuple.DisplayName,
504 dependency.Imported = true; 539 Attributes = record.GetInteger(4),
505 540 Imported = true
506 this.Facade.Provides.Add(dependency); 541 };
542
543 this.Section.Tuples.Add(tuple);
507 } 544 }
508 } 545 }
509 } 546 }
510 } 547 }
511 } 548 }
512 549
513 private string ResolveRelatedFile(string sourceFile, string relatedSource, string type, SourceLineNumber sourceLineNumbers, BindStage stage) 550 private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers, BindStage stage)
514 { 551 {
552 var checkedPaths = new List<string>();
553
515 foreach (var extension in this.BackendExtensions) 554 foreach (var extension in this.BackendExtensions)
516 { 555 {
517 var relatedFile = extension.ResolveRelatedFile(sourceFile, relatedSource, type, sourceLineNumbers, stage); 556 var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers, stage);
518 557
519 if (!String.IsNullOrEmpty(relatedFile)) 558 if (resolved?.CheckedPaths != null)
520 { 559 {
521 return relatedFile; 560 checkedPaths.AddRange(resolved.CheckedPaths);
561 }
562
563 if (!String.IsNullOrEmpty(resolved?.Path))
564 {
565 return resolved?.Path;
522 } 566 }
523 } 567 }
524 568
525 return null; 569 var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource);
570
571 if (!File.Exists(resolvedPath))
572 {
573 checkedPaths.Add(resolvedPath);
574 this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths));
575 }
576
577 return resolvedPath;
526 } 578 }
527 579
528 /// <summary> 580 /// <summary>
@@ -571,6 +623,5 @@ namespace WixToolset.Core.Burn.Bundles
571 623
572 return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property); 624 return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property);
573 } 625 }
574#endif
575 } 626 }
576} 627}
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
10 using System.Text; 10 using System.Text;
11 using System.Xml; 11 using System.Xml;
12 using WixToolset.Data; 12 using WixToolset.Data;
13 using WixToolset.Data.Tuples;
14 using WixToolset.Extensibility.Services;
13 using Dtf = WixToolset.Dtf.WindowsInstaller; 15 using Dtf = WixToolset.Dtf.WindowsInstaller;
14 16
15 /// <summary> 17 /// <summary>
@@ -17,80 +19,92 @@ namespace WixToolset.Core.Burn.Bundles
17 /// </summary> 19 /// </summary>
18 internal class ProcessMspPackageCommand 20 internal class ProcessMspPackageCommand
19 { 21 {
20#if TODO
21 private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'"; 22 private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'";
22 private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false); 23 private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false);
23 24
24 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } 25 public ProcessMspPackageCommand(IMessaging messaging, IntermediateSection section, PackageFacade facade, Dictionary<string, WixBundlePayloadTuple> payloadTuples)
26 {
27 this.Messaging = messaging;
28
29 this.AuthoredPayloads = payloadTuples;
30 this.Section = section;
31 this.Facade = facade;
32 }
33
34 public IMessaging Messaging { get; }
35
36 public Dictionary<string, WixBundlePayloadTuple> AuthoredPayloads { private get; set; }
25 37
26 public PackageFacade Facade { private get; set; } 38 public PackageFacade Facade { private get; set; }
27 39
28 public Table WixBundlePatchTargetCodeTable { private get; set; } 40 public IntermediateSection Section { get; }
29 41
30 /// <summary> 42 /// <summary>
31 /// Processes the Msp packages to add properties and payloads from the Msp packages. 43 /// Processes the Msp packages to add properties and payloads from the Msp packages.
32 /// </summary> 44 /// </summary>
33 public void Execute() 45 public void Execute()
34 { 46 {
35 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); 47 var packagePayload = this.AuthoredPayloads[this.Facade.PackageTuple.PayloadRef];
48
49 var mspPackage = (WixBundleMspPackageTuple)this.Facade.SpecificPackageTuple;
36 50
37 string sourcePath = packagePayload.FullFileName; 51 var sourcePath = packagePayload.SourceFile.Path;
38 52
39 try 53 try
40 { 54 {
41 // Read data out of the msp database... 55 // Read data out of the msp database...
42 using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) 56 using (var sumInfo = new Dtf.SummaryInfo(sourcePath, false))
43 { 57 {
44 this.Facade.MspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38); 58 mspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38);
45 } 59 }
46 60
47 using (Dtf.Database db = new Dtf.Database(sourcePath)) 61 using (var db = new Dtf.Database(sourcePath))
48 { 62 {
49 if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) 63 if (String.IsNullOrEmpty(this.Facade.PackageTuple.DisplayName))
50 { 64 {
51 this.Facade.Package.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName"); 65 this.Facade.PackageTuple.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName");
52 } 66 }
53 67
54 if (String.IsNullOrEmpty(this.Facade.Package.Description)) 68 if (String.IsNullOrEmpty(this.Facade.PackageTuple.Description))
55 { 69 {
56 this.Facade.Package.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description"); 70 this.Facade.PackageTuple.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description");
57 } 71 }
58 72
59 this.Facade.MspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName"); 73 mspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName");
60 } 74 }
61 75
62 this.ProcessPatchXml(packagePayload, sourcePath); 76 this.ProcessPatchXml(packagePayload, mspPackage, sourcePath);
63 } 77 }
64 catch (Dtf.InstallerException e) 78 catch (Dtf.InstallerException e)
65 { 79 {
66 Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); 80 this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message));
67 return; 81 return;
68 } 82 }
69 83
70 if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) 84 if (String.IsNullOrEmpty(this.Facade.PackageTuple.CacheId))
71 { 85 {
72 this.Facade.Package.CacheId = this.Facade.MspPackage.PatchCode; 86 this.Facade.PackageTuple.CacheId = mspPackage.PatchCode;
73 } 87 }
74 } 88 }
75 89
76 private void ProcessPatchXml(WixBundlePayloadRow packagePayload, string sourcePath) 90 private void ProcessPatchXml(WixBundlePayloadTuple packagePayload, WixBundleMspPackageTuple mspPackage, string sourcePath)
77 { 91 {
78 HashSet<string> uniqueTargetCodes = new HashSet<string>(); 92 var uniqueTargetCodes = new HashSet<string>();
79 93
80 string patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath); 94 var patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath);
81 95
82 XmlDocument doc = new XmlDocument(); 96 var doc = new XmlDocument();
83 doc.LoadXml(patchXml); 97 doc.LoadXml(patchXml);
84 98
85 XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); 99 var nsmgr = new XmlNamespaceManager(doc.NameTable);
86 nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); 100 nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd");
87 101
88 // Determine target ProductCodes and/or UpgradeCodes. 102 // Determine target ProductCodes and/or UpgradeCodes.
89 foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) 103 foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr))
90 { 104 {
91 // If this patch targets a product code, this is the best case. 105 // If this patch targets a product code, this is the best case.
92 XmlNode targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); 106 var targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr);
93 WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None; 107 var attributes = WixBundlePatchTargetCodeAttributes.None;
94 108
95 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) 109 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement))
96 { 110 {
@@ -105,45 +119,49 @@ namespace WixToolset.Core.Burn.Bundles
105 } 119 }
106 else // this patch targets an unknown number of products 120 else // this patch targets an unknown number of products
107 { 121 {
108 this.Facade.MspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; 122 mspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified;
109 } 123 }
110 } 124 }
111 125
112 string targetCode = targetCodeElement.InnerText; 126 var targetCode = targetCodeElement.InnerText;
113 127
114 if (uniqueTargetCodes.Add(targetCode)) 128 if (uniqueTargetCodes.Add(targetCode))
115 { 129 {
116 WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)this.WixBundlePatchTargetCodeTable.CreateRow(packagePayload.SourceLineNumbers); 130 var tuple = new WixBundlePatchTargetCodeTuple(packagePayload.SourceLineNumbers)
117 row.MspPackageId = packagePayload.Id; 131 {
118 row.TargetCode = targetCode; 132 PackageRef = packagePayload.Id.Id,
119 row.Attributes = attributes; 133 TargetCode = targetCode,
134 Attributes = attributes
135 };
136
137 this.Section.Tuples.Add(tuple);
120 } 138 }
121 } 139 }
122 140
123 // Suppress patch sequence data for improved performance. 141 // Suppress patch sequence data for improved performance.
124 XmlNode root = doc.DocumentElement; 142 var root = doc.DocumentElement;
125 foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) 143 foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr))
126 { 144 {
127 root.RemoveChild(node); 145 root.RemoveChild(node);
128 } 146 }
129 147
130 // Save the XML as compact as possible. 148 // Save the XML as compact as possible.
131 using (StringWriter writer = new StringWriter()) 149 using (var writer = new StringWriter())
132 { 150 {
133 XmlWriterSettings settings = new XmlWriterSettings() 151 var settings = new XmlWriterSettings()
134 { 152 {
135 Encoding = ProcessMspPackageCommand.XmlOutputEncoding, 153 Encoding = ProcessMspPackageCommand.XmlOutputEncoding,
136 Indent = false, 154 Indent = false,
137 NewLineChars = string.Empty, 155 NewLineChars = String.Empty,
138 NewLineHandling = NewLineHandling.Replace, 156 NewLineHandling = NewLineHandling.Replace,
139 }; 157 };
140 158
141 using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings)) 159 using (var xmlWriter = XmlWriter.Create(writer, settings))
142 { 160 {
143 doc.WriteTo(xmlWriter); 161 doc.WriteTo(xmlWriter);
144 } 162 }
145 163
146 this.Facade.MspPackage.PatchXml = writer.ToString(); 164 mspPackage.PatchXml = writer.ToString();
147 } 165 }
148 } 166 }
149 167
@@ -175,16 +193,6 @@ namespace WixToolset.Core.Burn.Bundles
175 return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property); 193 return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property);
176 } 194 }
177 195
178 private static bool TargetsCode(XmlNode node) 196 private static bool TargetsCode(XmlNode node) => "true" == node?.Attributes["Validate"]?.Value;
179 {
180 if (null != node)
181 {
182 XmlAttribute attr = node.Attributes["Validate"];
183 return null != attr && "true".Equals(attr.Value);
184 }
185
186 return false;
187 }
188#endif
189 } 197 }
190} 198}
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 @@
3namespace WixToolset.Core.Burn.Bundles 3namespace WixToolset.Core.Burn.Bundles
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic;
6 using WixToolset.Data; 7 using WixToolset.Data;
8 using WixToolset.Data.Tuples;
7 9
8 /// <summary> 10 /// <summary>
9 /// Processes the Msu packages to add properties and payloads from the Msu packages. 11 /// Processes the Msu packages to add properties and payloads from the Msu packages.
10 /// </summary> 12 /// </summary>
11 internal class ProcessMsuPackageCommand 13 internal class ProcessMsuPackageCommand
12 { 14 {
13#if TODO 15 public ProcessMsuPackageCommand(PackageFacade facade, Dictionary<string, WixBundlePayloadTuple> payloadTuples)
14 public RowDictionary<WixBundlePayload> AuthoredPayloads { private get; set; } 16 {
17 this.AuthoredPayloads = payloadTuples;
18 this.Facade = facade;
19 }
20
21 public Dictionary<string, WixBundlePayloadTuple> AuthoredPayloads { private get; set; }
15 22
16 public PackageFacade Facade { private get; set; } 23 public PackageFacade Facade { private get; set; }
17 24
18 public void Execute() 25 public void Execute()
19 { 26 {
20 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); 27 var packagePayload = this.AuthoredPayloads[this.Facade.PackageTuple.PayloadRef];
21 28
22 if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) 29 if (String.IsNullOrEmpty(this.Facade.PackageTuple.CacheId))
23 { 30 {
24 this.Facade.Package.CacheId = packagePayload.Hash; 31 this.Facade.PackageTuple.CacheId = packagePayload.Hash;
25 } 32 }
26 33
27 this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. 34 this.Facade.PackageTuple.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine.
28 } 35 }
29#endif
30 } 36 }
31} 37}
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
9 using System.Security.Cryptography; 9 using System.Security.Cryptography;
10 using System.Security.Cryptography.X509Certificates; 10 using System.Security.Cryptography.X509Certificates;
11 using System.Text; 11 using System.Text;
12 using WixToolset.Data;
13 using WixToolset.Data.Burn;
14 using WixToolset.Data.Tuples;
15 using WixToolset.Extensibility.Data;
16 using WixToolset.Extensibility.Services;
12 17
13 internal class ProcessPayloadsCommand 18 internal class ProcessPayloadsCommand
14 { 19 {
15#if TODO
16 private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); 20 private static readonly Version EmptyVersion = new Version(0, 0, 0, 0);
17 21
18 public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; } 22 public ProcessPayloadsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, IEnumerable<WixBundlePayloadTuple> payloads, PackagingType defaultPackaging, string layoutDirectory)
23 {
24 this.Messaging = serviceProvider.GetService<IMessaging>();
25
26 this.BackendHelper = backendHelper;
27 this.Payloads = payloads;
28 this.DefaultPackaging = defaultPackaging;
29 this.LayoutDirectory = layoutDirectory;
30 }
31
32 public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
19 33
20 public PackagingType DefaultPackaging { private get; set; } 34 private IMessaging Messaging { get; }
21 35
22 public string LayoutDirectory { private get; set; } 36 private IBackendHelper BackendHelper { get; }
23 37
24 public IEnumerable<FileTransfer> FileTransfers { get; private set; } 38 private IEnumerable<WixBundlePayloadTuple> Payloads { get; }
39
40 private PackagingType DefaultPackaging { get; }
41
42 private string LayoutDirectory { get; }
25 43
26 public void Execute() 44 public void Execute()
27 { 45 {
28 List<FileTransfer> fileTransfers = new List<FileTransfer>(); 46 var fileTransfers = new List<IFileTransfer>();
29 47
30 foreach (WixBundlePayloadRow payload in this.Payloads) 48 foreach (var payload in this.Payloads)
31 { 49 {
32 string normalizedPath = payload.Name.Replace('\\', '/'); 50 var normalizedPath = payload.Name.Replace('\\', '/');
33 if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../")) 51 if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../"))
34 { 52 {
35 Messaging.Instance.OnMessage(WixErrors.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name)); 53 this.Messaging.Write(ErrorMessages.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name));
36 } 54 }
37 55
38 // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden 56 // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden
39 // in the .wixlib). 57 // in the .wixlib).
40 ObjectField field = (ObjectField)payload.Fields[2]; 58 var sourceFile = payload.SourceFile;
41 payload.ContentFile = !field.EmbeddedFileIndex.HasValue; 59 payload.ContentFile = !sourceFile.EmbeddedFileIndex.HasValue;
42 60
43 this.UpdatePayloadPackagingType(payload); 61 this.UpdatePayloadPackagingType(payload);
44 62
45 if (String.IsNullOrEmpty(payload.SourceFile)) 63 if (String.IsNullOrEmpty(sourceFile?.Path))
46 { 64 {
47 // Remote payloads obviously cannot be embedded. 65 // Remote payloads obviously cannot be embedded.
48 Debug.Assert(PackagingType.Embedded != payload.Packaging); 66 Debug.Assert(PackagingType.Embedded != payload.Packaging);
49 } 67 }
50 else // not a remote payload so we have a lot more to update. 68 else // not a remote payload so we have a lot more to update.
51 { 69 {
52 this.UpdatePayloadFileInformation(payload); 70 this.UpdatePayloadFileInformation(payload, sourceFile);
53 71
54 this.UpdatePayloadVersionInformation(payload); 72 this.UpdatePayloadVersionInformation(payload, sourceFile);
55 73
56 // External payloads need to be transfered. 74 // External payloads need to be transfered.
57 if (PackagingType.External == payload.Packaging) 75 if (PackagingType.External == payload.Packaging)
58 { 76 {
59 FileTransfer transfer; 77 var transfer = this.BackendHelper.CreateFileTransfer(sourceFile.Path, Path.Combine(this.LayoutDirectory, payload.Name), false, payload.SourceLineNumbers);
60 if (FileTransfer.TryCreate(payload.FullFileName, Path.Combine(this.LayoutDirectory, payload.Name), false, "Payload", payload.SourceLineNumbers, out transfer)) 78 fileTransfers.Add(transfer);
61 {
62 fileTransfers.Add(transfer);
63 }
64 } 79 }
65 } 80 }
66 } 81 }
@@ -68,41 +83,41 @@ namespace WixToolset.Core.Burn.Bundles
68 this.FileTransfers = fileTransfers; 83 this.FileTransfers = fileTransfers;
69 } 84 }
70 85
71 private void UpdatePayloadPackagingType(WixBundlePayloadRow payload) 86 private void UpdatePayloadPackagingType(WixBundlePayloadTuple payload)
72 { 87 {
73 if (PackagingType.Unknown == payload.Packaging) 88 if (PackagingType.Unknown == payload.Packaging)
74 { 89 {
75 if (YesNoDefaultType.Yes == payload.Compressed) 90 if (!payload.Compressed.HasValue)
76 { 91 {
77 payload.Packaging = PackagingType.Embedded; 92 payload.Packaging = this.DefaultPackaging;
78 } 93 }
79 else if (YesNoDefaultType.No == payload.Compressed) 94 else if (payload.Compressed.Value)
80 { 95 {
81 payload.Packaging = PackagingType.External; 96 payload.Packaging = PackagingType.Embedded;
82 } 97 }
83 else 98 else
84 { 99 {
85 payload.Packaging = this.DefaultPackaging; 100 payload.Packaging = PackagingType.External;
86 } 101 }
87 } 102 }
88 103
89 // Embedded payloads that are not assigned a container already are placed in the default attached 104 // Embedded payloads that are not assigned a container already are placed in the default attached
90 // container. 105 // container.
91 if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.Container)) 106 if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.ContainerRef))
92 { 107 {
93 payload.Container = Compiler.BurnDefaultAttachedContainerId; 108 payload.ContainerRef = BurnConstants.BurnDefaultAttachedContainerName;
94 } 109 }
95 } 110 }
96 111
97 private void UpdatePayloadFileInformation(WixBundlePayloadRow payload) 112 private void UpdatePayloadFileInformation(WixBundlePayloadTuple payload, IntermediateFieldPathValue sourceFile)
98 { 113 {
99 FileInfo fileInfo = new FileInfo(payload.SourceFile); 114 var fileInfo = new FileInfo(sourceFile.Path);
100 115
101 if (null != fileInfo) 116 if (null != fileInfo)
102 { 117 {
103 payload.FileSize = (int)fileInfo.Length; 118 payload.FileSize = (int)fileInfo.Length;
104 119
105 payload.Hash = Common.GetFileHash(fileInfo.FullName); 120 payload.Hash = BundleHashAlgorithm.Hash(fileInfo);
106 121
107 // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation. 122 // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation.
108 if (payload.EnableSignatureValidation) 123 if (payload.EnableSignatureValidation)
@@ -122,9 +137,10 @@ namespace WixToolset.Core.Burn.Bundles
122 byte[] publicKeyIdentifierHash = new byte[128]; 137 byte[] publicKeyIdentifierHash = new byte[128];
123 uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length; 138 uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length;
124 139
125 WixToolset.Core.Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize); 140 Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize);
126 StringBuilder sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2); 141
127 for (int i = 0; i < publicKeyIdentifierHashSize; ++i) 142 var sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2);
143 for (var i = 0; i < publicKeyIdentifierHashSize; ++i)
128 { 144 {
129 sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]); 145 sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]);
130 } 146 }
@@ -136,14 +152,14 @@ namespace WixToolset.Core.Burn.Bundles
136 } 152 }
137 } 153 }
138 154
139 private void UpdatePayloadVersionInformation(WixBundlePayloadRow payload) 155 private void UpdatePayloadVersionInformation(WixBundlePayloadTuple payload, IntermediateFieldPathValue sourceFile)
140 { 156 {
141 FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(payload.SourceFile); 157 var versionInfo = FileVersionInfo.GetVersionInfo(sourceFile.Path);
142 158
143 if (null != versionInfo) 159 if (null != versionInfo)
144 { 160 {
145 // Use the fixed version info block for the file since the resource text may not be a dotted quad. 161 // Use the fixed version info block for the file since the resource text may not be a dotted quad.
146 Version version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); 162 var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart);
147 163
148 if (ProcessPayloadsCommand.EmptyVersion != version) 164 if (ProcessPayloadsCommand.EmptyVersion != version)
149 { 165 {
@@ -154,6 +170,5 @@ namespace WixToolset.Core.Burn.Bundles
154 payload.DisplayName = versionInfo.ProductName; 170 payload.DisplayName = versionInfo.ProductName;
155 } 171 }
156 } 172 }
157#endif
158 } 173 }
159} 174}
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
9 using System.Runtime.InteropServices; 9 using System.Runtime.InteropServices;
10 using System.Text; 10 using System.Text;
11 using WixToolset.Data; 11 using WixToolset.Data;
12 using WixToolset.Data.Tuples;
13 using WixToolset.Extensibility.Services;
12 14
13 internal class VerifyPayloadsWithCatalogCommand 15 internal class VerifyPayloadsWithCatalogCommand
14 { 16 {
15#if TODO 17 public VerifyPayloadsWithCatalogCommand(IMessaging messaging, IEnumerable<WixBundleCatalogTuple> catalogs, IEnumerable<WixBundlePayloadTuple> payloads)
16 public IEnumerable<WixBundleCatalogRow> Catalogs { private get; set; } 18 {
19 this.Messaging = messaging;
20 this.Catalogs = catalogs;
21 this.Payloads = payloads;
22 }
23
24 private IMessaging Messaging { get; }
25
26 private IEnumerable<WixBundleCatalogTuple> Catalogs { get; }
17 27
18 public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; } 28 private IEnumerable<WixBundlePayloadTuple> Payloads { get; }
19 29
20 public void Execute() 30 public void Execute()
21 { 31 {
22 List<CatalogIdWithPath> catalogIdsWithPaths = this.Catalogs 32 var catalogIdsWithPaths = this.Catalogs
23 .Join(this.Payloads, 33 .Join(this.Payloads,
24 catalog => catalog.Payload, 34 catalog => catalog.PayloadRef,
25 payload => payload.Id, 35 payload => payload.Id.Id,
26 (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id, FullPath = Path.GetFullPath(payload.SourceFile) }) 36 (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id.Id, FullPath = Path.GetFullPath(payload.SourceFile.Path) })
27 .ToList(); 37 .ToList();
28 38
29 foreach (WixBundlePayloadRow payloadInfo in this.Payloads) 39 foreach (var payloadInfo in this.Payloads)
30 { 40 {
31 // Payloads that are not embedded should be verfied. 41 // Payloads that are not embedded should be verfied.
32 if (String.IsNullOrEmpty(payloadInfo.EmbeddedId)) 42 if (String.IsNullOrEmpty(payloadInfo.EmbeddedId))
33 { 43 {
34 bool validated = false; 44 var sourceFile = payloadInfo.SourceFile.Path;
45 var validated = false;
35 46
36 foreach (CatalogIdWithPath catalog in catalogIdsWithPaths) 47 foreach (var catalog in catalogIdsWithPaths)
37 { 48 {
38 if (!validated) 49 if (!validated)
39 { 50 {
@@ -41,11 +52,10 @@ namespace WixToolset.Core.Burn.Bundles
41 uint cryptHashSize = 20; 52 uint cryptHashSize = 20;
42 byte[] cryptHashBytes = new byte[cryptHashSize]; 53 byte[] cryptHashBytes = new byte[cryptHashSize];
43 int error; 54 int error;
44 IntPtr fileHandle = IntPtr.Zero; 55 using (var payloadStream = File.OpenRead(sourceFile))
45 using (FileStream payloadStream = File.OpenRead(payloadInfo.FullFileName))
46 { 56 {
47 // Get the file handle 57 // Get the file handle
48 fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle(); 58 var fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle();
49 59
50 // 20 bytes is usually the hash size. Future hashes may be bigger 60 // 20 bytes is usually the hash size. Future hashes may be bigger
51 if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) 61 if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0))
@@ -64,7 +74,7 @@ namespace WixToolset.Core.Burn.Bundles
64 74
65 if (0 != error) 75 if (0 != error)
66 { 76 {
67 Messaging.Instance.OnMessage(WixErrors.CatalogFileHashFailed(payloadInfo.FullFileName, error)); 77 this.Messaging.Write(ErrorMessages.CatalogFileHashFailed(sourceFile, error));
68 } 78 }
69 } 79 }
70 } 80 }
@@ -79,15 +89,15 @@ namespace WixToolset.Core.Burn.Bundles
79 catalogData.pbCalculatedFileHash = Marshal.AllocCoTaskMem((int)cryptHashSize); 89 catalogData.pbCalculatedFileHash = Marshal.AllocCoTaskMem((int)cryptHashSize);
80 Marshal.Copy(cryptHashBytes, 0, catalogData.pbCalculatedFileHash, (int)cryptHashSize); 90 Marshal.Copy(cryptHashBytes, 0, catalogData.pbCalculatedFileHash, (int)cryptHashSize);
81 91
82 StringBuilder hashString = new StringBuilder(); 92 var hashString = new StringBuilder();
83 foreach (byte hashByte in cryptHashBytes) 93 foreach (var hashByte in cryptHashBytes)
84 { 94 {
85 hashString.Append(hashByte.ToString("X2")); 95 hashString.Append(hashByte.ToString("X2"));
86 } 96 }
87 catalogData.pcwszMemberTag = hashString.ToString(); 97 catalogData.pcwszMemberTag = hashString.ToString();
88 98
89 // The file names need to be lower case for older OSes 99 // The file names need to be lower case for older OSes
90 catalogData.pcwszMemberFilePath = payloadInfo.FullFileName.ToLowerInvariant(); 100 catalogData.pcwszMemberFilePath = sourceFile.ToLowerInvariant();
91 catalogData.pcwszCatalogFilePath = catalog.FullPath.ToLowerInvariant(); 101 catalogData.pcwszCatalogFilePath = catalog.FullPath.ToLowerInvariant();
92 102
93 // Create WINTRUST_DATA structure 103 // Create WINTRUST_DATA structure
@@ -108,7 +118,7 @@ namespace WixToolset.Core.Burn.Bundles
108 long verifyResult = VerifyInterop.WinVerifyTrust(noWindow, ref verifyGuid, ref trustData); 118 long verifyResult = VerifyInterop.WinVerifyTrust(noWindow, ref verifyGuid, ref trustData);
109 if (0 == verifyResult) 119 if (0 == verifyResult)
110 { 120 {
111 payloadInfo.Catalog = catalog.Id; 121 payloadInfo.CatalogRef = catalog.Id;
112 validated = true; 122 validated = true;
113 break; 123 break;
114 } 124 }
@@ -132,7 +142,7 @@ namespace WixToolset.Core.Burn.Bundles
132 // Error message if the file was not validated by one of the catalogs 142 // Error message if the file was not validated by one of the catalogs
133 if (!validated) 143 if (!validated)
134 { 144 {
135 Messaging.Instance.OnMessage(WixErrors.CatalogVerificationFailed(payloadInfo.FullFileName)); 145 this.Messaging.Write(ErrorMessages.CatalogVerificationFailed(sourceFile));
136 } 146 }
137 } 147 }
138 } 148 }
@@ -144,6 +154,5 @@ namespace WixToolset.Core.Burn.Bundles
144 154
145 public string FullPath { get; set; } 155 public string FullPath { get; set; }
146 } 156 }
147#endif
148 } 157 }
149} 158}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. 1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 2
3namespace WixToolset.Core.Burn 3namespace WixToolset.Core.Burn
4{ 4{
5 using System; 5 using System;
6 using System.IO; 6 using System.IO;
7 using WixToolset.Extensibility; 7 using WixToolset.Extensibility;
8 using WixToolset.Extensibility.Data;
9 8
10 internal class BurnBackendFactory : IBackendFactory 9 internal class BurnBackendFactory : IBackendFactory
11 { 10 {
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 @@
3namespace WixToolset 3namespace WixToolset
4{ 4{
5 using System; 5 using System;
6 using System.Collections;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices; 6 using System.Runtime.InteropServices;
9 7
10 internal class VerifyInterop 8 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 @@
15 </ItemGroup> 15 </ItemGroup>
16 16
17 <ItemGroup> 17 <ItemGroup>
18 <PackageReference Include="System.Security.Permissions" Version="4.6.0" />
18 <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" /> 19 <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" />
19 <PackageReference Include="WixToolset.Dtf.Resources" Version="4.0.*" NoWarn="NU1701" /> 20 <PackageReference Include="WixToolset.Dtf.Resources" Version="4.0.*" NoWarn="NU1701" />
20 <PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.*" NoWarn="NU1701" /> 21 <PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.*" NoWarn="NU1701" />
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
30 30
31 this.BackendHelper = context.ServiceProvider.GetService<IBackendHelper>(); 31 this.BackendHelper = context.ServiceProvider.GetService<IBackendHelper>();
32 32
33 this.PathResolver = this.ServiceProvider.GetService<IPathResolver>();
34
33 this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions(); 35 this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions();
34 36
35 this.CabbingThreadCount = context.CabbingThreadCount; 37 this.CabbingThreadCount = context.CabbingThreadCount;
@@ -54,6 +56,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
54 56
55 private IBackendHelper BackendHelper { get; } 57 private IBackendHelper BackendHelper { get; }
56 58
59 private IPathResolver PathResolver { get; }
60
57 private int Codepage { get; } 61 private int Codepage { get; }
58 62
59 private int CabbingThreadCount { get; } 63 private int CabbingThreadCount { get; }
@@ -241,7 +245,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
241 245
242 // Set generated component guids. 246 // Set generated component guids.
243 { 247 {
244 var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, section); 248 var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, this.PathResolver, section);
245 command.Execute(); 249 command.Execute();
246 } 250 }
247 251
@@ -501,7 +505,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
501 // Process uncompressed files. 505 // Process uncompressed files.
502 if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) 506 if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any())
503 { 507 {
504 var command = new ProcessUncompressedFilesCommand(section, this.BackendHelper); 508 var command = new ProcessUncompressedFilesCommand(section, this.BackendHelper, this.PathResolver);
505 command.Compressed = compressed; 509 command.Compressed = compressed;
506 command.FileFacades = uncompressedFiles; 510 command.FileFacades = uncompressedFiles;
507 command.LayoutDirectory = layoutDirectory; 511 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
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using System.Linq; 8 using System.Linq;
9 using WixToolset.Core.Native;
10 using WixToolset.Data; 9 using WixToolset.Data;
11 using WixToolset.Data.Tuples; 10 using WixToolset.Data.Tuples;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services; 12 using WixToolset.Extensibility.Services;
13 13
14 /// <summary> 14 /// <summary>
@@ -16,10 +16,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
16 /// </summary> 16 /// </summary>
17 internal class CalculateComponentGuids 17 internal class CalculateComponentGuids
18 { 18 {
19 internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IntermediateSection section) 19 internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section)
20 { 20 {
21 this.Messaging = messaging; 21 this.Messaging = messaging;
22 this.BackendHelper = helper; 22 this.BackendHelper = helper;
23 this.PathResolver = pathResolver;
23 this.Section = section; 24 this.Section = section;
24 } 25 }
25 26
@@ -27,12 +28,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
27 28
28 private IBackendHelper BackendHelper { get; } 29 private IBackendHelper BackendHelper { get; }
29 30
31 private IPathResolver PathResolver { get; }
32
30 private IntermediateSection Section { get; } 33 private IntermediateSection Section { get; }
31 34
32 public void Execute() 35 public void Execute()
33 { 36 {
34 Dictionary<string, RegistryTuple> registryKeyRows = null; 37 Dictionary<string, RegistryTuple> registryKeyRows = null;
35 Dictionary<string, ResolvedDirectory> targetPathsByDirectoryId = null; 38 Dictionary<string, IResolvedDirectory> targetPathsByDirectoryId = null;
36 Dictionary<string, string> componentIdGenSeeds = null; 39 Dictionary<string, string> componentIdGenSeeds = null;
37 Dictionary<string, List<FileTuple>> filesByComponentId = null; 40 Dictionary<string, List<FileTuple>> filesByComponentId = null;
38 41
@@ -73,7 +76,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
73 { 76 {
74 var directories = this.Section.Tuples.OfType<DirectoryTuple>().ToList(); 77 var directories = this.Section.Tuples.OfType<DirectoryTuple>().ToList();
75 78
76 targetPathsByDirectoryId = new Dictionary<string, ResolvedDirectory>(directories.Count); 79 targetPathsByDirectoryId = new Dictionary<string, IResolvedDirectory>(directories.Count);
77 80
78 // Get the target paths for all directories. 81 // Get the target paths for all directories.
79 foreach (var directory in directories) 82 foreach (var directory in directories)
@@ -86,7 +89,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
86 continue; 89 continue;
87 } 90 }
88 91
89 targetPathsByDirectoryId.Add(directory.Id.Id, new ResolvedDirectory(directory.ParentDirectoryRef, directory.Name)); 92 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name);
93 targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory);
90 } 94 }
91 } 95 }
92 96
@@ -131,7 +135,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
131 if (fileRow.Id.Id == componentTuple.KeyPath) 135 if (fileRow.Id.Id == componentTuple.KeyPath)
132 { 136 {
133 // calculate the key file's canonical target path 137 // calculate the key file's canonical target path
134 string directoryPath = PathResolver.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentTuple.DirectoryRef, true); 138 string directoryPath = this.PathResolver.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentTuple.DirectoryRef, true);
135 string fileName = Common.GetName(fileRow.Name, false, true).ToLowerInvariant(); 139 string fileName = Common.GetName(fileRow.Name, false, true).ToLowerInvariant();
136 path = Path.Combine(directoryPath, fileName); 140 path = Path.Combine(directoryPath, fileName);
137 141
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
8 using System.IO; 8 using System.IO;
9 using System.Linq; 9 using System.Linq;
10 using System.Runtime.InteropServices; 10 using System.Runtime.InteropServices;
11 using System.Threading;
12 using WixToolset.Core.Bind; 11 using WixToolset.Core.Bind;
13 using WixToolset.Data; 12 using WixToolset.Data;
14 using WixToolset.Data.Tuples; 13 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
105 this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple, output); 105 this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple, output);
106 break; 106 break;
107 107
108 case TupleDefinitionType.MsiFileHash:
109 this.AddMsiFileHashTuple((MsiFileHashTuple)tuple, output);
110 break;
111
108 case TupleDefinitionType.MsiServiceConfig: 112 case TupleDefinitionType.MsiServiceConfig:
109 this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple, output); 113 this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple, output);
110 break; 114 break;
@@ -500,6 +504,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
500 row[4] = tuple.Source; 504 row[4] = tuple.Source;
501 } 505 }
502 506
507 private void AddMsiFileHashTuple(MsiFileHashTuple tuple, Output output)
508 {
509 var table = output.EnsureTable(this.TableDefinitions["MsiFileHash"]);
510 var row = table.CreateRow(tuple.SourceLineNumbers);
511 row[0] = tuple.Id.Id;
512 row[1] = tuple.Options;
513 row[2] = tuple.HashPart1;
514 row[3] = tuple.HashPart2;
515 row[4] = tuple.HashPart3;
516 row[5] = tuple.HashPart4;
517 }
518
503 private void AddMsiServiceConfigTuple(MsiServiceConfigTuple tuple, Output output) 519 private void AddMsiServiceConfigTuple(MsiServiceConfigTuple tuple, Output output)
504 { 520 {
505 var events = tuple.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; 521 var events = tuple.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0;
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
18 /// </summary> 18 /// </summary>
19 internal class ProcessUncompressedFilesCommand 19 internal class ProcessUncompressedFilesCommand
20 { 20 {
21 public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper) 21 public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver)
22 { 22 {
23 this.Section = section; 23 this.Section = section;
24 this.BackendHelper = backendHelper; 24 this.BackendHelper = backendHelper;
25 this.PathResolver = pathResolver;
25 } 26 }
26 27
27 private IntermediateSection Section { get; } 28 private IntermediateSection Section { get; }
28 29
29 public IBackendHelper BackendHelper { get; } 30 public IBackendHelper BackendHelper { get; }
30 31
32 public IPathResolver PathResolver { get; }
33
31 public string DatabasePath { private get; set; } 34 public string DatabasePath { private get; set; }
32 35
33 public IEnumerable<FileFacade> FileFacades { private get; set; } 36 public IEnumerable<FileFacade> FileFacades { private get; set; }
@@ -50,7 +53,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
50 53
51 var trackedFiles = new List<ITrackedFile>(); 54 var trackedFiles = new List<ITrackedFile>();
52 55
53 var directories = new Dictionary<string, ResolvedDirectory>(); 56 var directories = new Dictionary<string, IResolvedDirectory>();
54 57
55 var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId); 58 var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId);
56 59
@@ -69,7 +72,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
69 72
70 string sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage); 73 string sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage);
71 74
72 directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); 75 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName);
76
77 directories.Add(directoryRecord.GetString(1), resolvedDirectory);
73 } 78 }
74 } 79 }
75 } 80 }
@@ -99,7 +104,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
99 throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.Id.Id)); 104 throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.Id.Id));
100 } 105 }
101 106
102 relativeFileLayoutPath = PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); 107 relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage);
103 } 108 }
104 109
105 // finally put together the base media layout path and the relative file layout path 110 // 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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 /// <summary>
6 /// Structure used for resolved directory information.
7 /// </summary>
8 internal struct ResolvedDirectory
9 {
10 /// <summary>
11 /// Constructor for ResolvedDirectory.
12 /// </summary>
13 /// <param name="directoryParent">Parent directory.</param>
14 /// <param name="name">The directory name.</param>
15 public ResolvedDirectory(string directoryParent, string name)
16 {
17 this.DirectoryParent = directoryParent;
18 this.Name = name;
19 this.Path = null;
20 }
21
22 /// <summary>The directory parent.</summary>
23 public string DirectoryParent { get; set; }
24
25 /// <summary>The name of this directory.</summary>
26 public string Name { get; set; }
27
28 /// <summary>The path of this directory.</summary>
29 public string Path { get; set; }
30 }
31}
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
162 this.Section.Tuples.Add(facade.Hash); 162 this.Section.Tuples.Add(facade.Hash);
163 } 163 }
164 164
165 facade.Hash.FileRef = facade.File.Id.Id;
166 facade.Hash.Options = 0; 165 facade.Hash.Options = 0;
167 facade.Hash.HashPart1 = hash[0]; 166 facade.Hash.HashPart1 = hash[0];
168 facade.Hash.HashPart2 = hash[1]; 167 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
30 { 30 {
31 // If the uri to the file that contains the embedded file does not already have embedded files 31 // If the uri to the file that contains the embedded file does not already have embedded files
32 // being extracted, create the dictionary to track that. 32 // being extracted, create the dictionary to track that.
33 if (!filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) 33 if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts))
34 { 34 {
35 extracts = new SortedList<int, string>(); 35 extracts = new SortedList<int, string>();
36 filesWithEmbeddedFiles.Add(uri, extracts); 36 this.filesWithEmbeddedFiles.Add(uri, extracts);
37 } 37 }
38 38
39 // If the embedded file is not already tracked in the dictionary of extracts, add it. 39 // If the embedded file is not already tracked in the dictionary of extracts, add it.
@@ -52,7 +52,7 @@ namespace WixToolset.Core.Bind
52 52
53 public IEnumerable<ExpectedExtractFile> GetExpectedEmbeddedFiles() 53 public IEnumerable<ExpectedExtractFile> GetExpectedEmbeddedFiles()
54 { 54 {
55 foreach (var uriWithExtracts in filesWithEmbeddedFiles) 55 foreach (var uriWithExtracts in this.filesWithEmbeddedFiles)
56 { 56 {
57 foreach (var extracts in uriWithExtracts.Value) 57 foreach (var extracts in uriWithExtracts.Value)
58 { 58 {
@@ -68,7 +68,7 @@ namespace WixToolset.Core.Bind
68 68
69 public IEnumerable<ExpectedExtractFile> GetExtractFilesForUri(Uri uri) 69 public IEnumerable<ExpectedExtractFile> GetExtractFilesForUri(Uri uri)
70 { 70 {
71 if (!filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) 71 if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts))
72 { 72 {
73 extracts = new SortedList<int, string>(); 73 extracts = new SortedList<int, string>();
74 } 74 }
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
70 /// <param name="type">Optional type of source file being resolved.</param> 70 /// <param name="type">Optional type of source file being resolved.</param>
71 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param> 71 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param>
72 /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param> 72 /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param>
73 /// <param name="alreadyCheckedPaths">Optional collection of paths already checked.</param>
73 /// <returns>Should return a valid path for the stream to be imported.</returns> 74 /// <returns>Should return a valid path for the stream to be imported.</returns>
74 public string ResolveFile(string source, IntermediateTupleDefinition tupleDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage) 75 public string ResolveFile(string source, IntermediateTupleDefinition tupleDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable<string> alreadyCheckedPaths = null)
75 { 76 {
76 var checkedPaths = new List<string>(); 77 var checkedPaths = new List<string>();
77 78
79 if (alreadyCheckedPaths != null)
80 {
81 checkedPaths.AddRange(alreadyCheckedPaths);
82 }
83
78 foreach (var extension in this.ResolverExtensions) 84 foreach (var extension in this.ResolverExtensions)
79 { 85 {
80 var resolved = extension.ResolveFile(source, tupleDefinition, sourceLineNumbers, bindStage); 86 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
113 } 113 }
114 } 114 }
115 115
116 public static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary<string, string> resolutionData) 116 private static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary<string, string> resolutionData)
117 { 117 {
118 var matches = Common.WixVariableRegex.Matches(value); 118 var matches = Common.WixVariableRegex.Matches(value);
119 119
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. 1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 2
3namespace WixToolset.Core 3namespace WixToolset.Core
4{ 4{
@@ -19,6 +19,8 @@ namespace WixToolset.Core
19 19
20 public IEnumerable<BindPath> BindPaths { get; set; } 20 public IEnumerable<BindPath> BindPaths { get; set; }
21 21
22 public string BurnStubPath { get; set; }
23
22 public int CabbingThreadCount { get; set; } 24 public int CabbingThreadCount { get; set; }
23 25
24 public string CabCachePath { get; set; } 26 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
122 } 122 }
123 else 123 else
124 { 124 {
125 this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths); 125 this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, this.commandLine.BurnStubPath);
126 } 126 }
127 } 127 }
128 } 128 }
@@ -257,7 +257,7 @@ namespace WixToolset.Core.CommandLine
257 return linker.Link(context); 257 return linker.Link(context);
258 } 258 }
259 259
260 private void BindPhase(Intermediate output, IEnumerable<Localization> localizations, IEnumerable<string> filterCultures, string cabCachePath, IEnumerable<IBindPath> bindPaths) 260 private void BindPhase(Intermediate output, IEnumerable<Localization> localizations, IEnumerable<string> filterCultures, string cabCachePath, IEnumerable<IBindPath> bindPaths, string burnStubPath)
261 { 261 {
262 var intermediateFolder = this.IntermediateFolder; 262 var intermediateFolder = this.IntermediateFolder;
263 if (String.IsNullOrEmpty(intermediateFolder)) 263 if (String.IsNullOrEmpty(intermediateFolder))
@@ -290,6 +290,7 @@ namespace WixToolset.Core.CommandLine
290 { 290 {
291 var context = this.ServiceProvider.GetService<IBindContext>(); 291 var context = this.ServiceProvider.GetService<IBindContext>();
292 //context.CabbingThreadCount = this.CabbingThreadCount; 292 //context.CabbingThreadCount = this.CabbingThreadCount;
293 context.BurnStubPath = burnStubPath;
293 context.CabCachePath = cabCachePath; 294 context.CabCachePath = cabCachePath;
294 context.Codepage = resolveResult.Codepage; 295 context.Codepage = resolveResult.Codepage;
295 //context.DefaultCompressionLevel = this.DefaultCompressionLevel; 296 //context.DefaultCompressionLevel = this.DefaultCompressionLevel;
@@ -399,6 +400,8 @@ namespace WixToolset.Core.CommandLine
399 400
400 public List<IBindPath> BindPaths { get; } = new List<IBindPath>(); 401 public List<IBindPath> BindPaths { get; } = new List<IBindPath>();
401 402
403 public string BurnStubPath { get; private set; }
404
402 public string CabCachePath { get; private set; } 405 public string CabCachePath { get; private set; }
403 406
404 public List<string> Cultures { get; } = new List<string>(); 407 public List<string> Cultures { get; } = new List<string>();
@@ -480,6 +483,10 @@ namespace WixToolset.Core.CommandLine
480 } 483 }
481 break; 484 break;
482 } 485 }
486 case "burnstub":
487 this.BurnStubPath = parser.GetNextArgumentOrError(arg);
488 return true;
489
483 case "cc": 490 case "cc":
484 this.CabCachePath = parser.GetNextArgumentOrError(arg); 491 this.CabCachePath = parser.GetNextArgumentOrError(arg);
485 return true; 492 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
818 { 818 {
819 this.Core.AddTuple(new IconTuple(sourceLineNumbers, id) 819 this.Core.AddTuple(new IconTuple(sourceLineNumbers, id)
820 { 820 {
821 Data = sourceFile 821 Data = new IntermediateFieldPathValue { Path = sourceFile }
822 }); 822 });
823 } 823 }
824 824
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
9 using System.IO; 9 using System.IO;
10 using System.Xml.Linq; 10 using System.Xml.Linq;
11 using WixToolset.Data; 11 using WixToolset.Data;
12 using WixToolset.Data.Burn;
12 using WixToolset.Data.Tuples; 13 using WixToolset.Data.Tuples;
13 using WixToolset.Extensibility; 14 using WixToolset.Extensibility;
14 15
@@ -17,15 +18,9 @@ namespace WixToolset.Core
17 /// </summary> 18 /// </summary>
18 internal partial class Compiler : ICompiler 19 internal partial class Compiler : ICompiler
19 { 20 {
20 public static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Private, "WixUXContainer"); 21 private static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Private, BurnConstants.BurnUXContainerName);
21 public static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Private, "WixAttachedContainer"); 22 private static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Private, BurnConstants.BurnDefaultAttachedContainerName);
22 public static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Private, "BundleLayoutOnlyPayloads"); 23 private static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Private, BurnConstants.BundleLayoutOnlyPayloadsName);
23
24 // The following constants must stay in sync with src\burn\engine\core.h
25 private const string BURN_BUNDLE_NAME = "WixBundleName";
26 private const string BURN_BUNDLE_ORIGINAL_SOURCE = "WixBundleOriginalSource";
27 private const string BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER = "WixBundleOriginalSourceFolder";
28 private const string BURN_BUNDLE_LAST_USED_SOURCE = "WixBundleLastUsedSource";
29 24
30 /// <summary> 25 /// <summary>
31 /// Parses an ApprovedExeForElevation element. 26 /// Parses an ApprovedExeForElevation element.
@@ -78,11 +73,11 @@ namespace WixToolset.Core
78 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); 73 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
79 } 74 }
80 75
81 var attributes = BundleApprovedExeForElevationAttributes.None; 76 var attributes = WixApprovedExeForElevationAttributes.None;
82 77
83 if (win64 == YesNoType.Yes) 78 if (win64 == YesNoType.Yes)
84 { 79 {
85 attributes |= BundleApprovedExeForElevationAttributes.Win64; 80 attributes |= WixApprovedExeForElevationAttributes.Win64;
86 } 81 }
87 82
88 this.Core.ParseForExtensionElements(node); 83 this.Core.ParseForExtensionElements(node);
@@ -92,7 +87,7 @@ namespace WixToolset.Core
92 var tuple = new WixApprovedExeForElevationTuple(sourceLineNumbers, id) 87 var tuple = new WixApprovedExeForElevationTuple(sourceLineNumbers, id)
93 { 88 {
94 Key = key, 89 Key = key,
95 Value = valueName, 90 ValueName = valueName,
96 Attributes = attributes 91 Attributes = attributes
97 }; 92 };
98 93
@@ -110,8 +105,7 @@ namespace WixToolset.Core
110 string copyright = null; 105 string copyright = null;
111 string aboutUrl = null; 106 string aboutUrl = null;
112 var compressed = YesNoDefaultType.Default; 107 var compressed = YesNoDefaultType.Default;
113 var disableModify = -1; 108 WixBundleAttributes attributes = 0;
114 var disableRemove = YesNoType.NotSet;
115 string helpTelephone = null; 109 string helpTelephone = null;
116 string helpUrl = null; 110 string helpUrl = null;
117 string manufacturer = null; 111 string manufacturer = null;
@@ -152,13 +146,12 @@ namespace WixToolset.Core
152 switch (value) 146 switch (value)
153 { 147 {
154 case "button": 148 case "button":
155 disableModify = 2; 149 attributes |= WixBundleAttributes.SingleChangeUninstallButton;
156 break; 150 break;
157 case "yes": 151 case "yes":
158 disableModify = 1; 152 attributes |= WixBundleAttributes.DisableModify;
159 break; 153 break;
160 case "no": 154 case "no":
161 disableModify = 0;
162 break; 155 break;
163 default: 156 default:
164 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); 157 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no"));
@@ -166,10 +159,10 @@ namespace WixToolset.Core
166 } 159 }
167 break; 160 break;
168 case "DisableRemove": 161 case "DisableRemove":
169 disableRemove = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); 162 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
170 break; 163 {
171 case "DisableRepair": 164 attributes |= WixBundleAttributes.DisableRemove;
172 this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); 165 }
173 break; 166 break;
174 case "HelpTelephone": 167 case "HelpTelephone":
175 helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib); 168 helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
@@ -239,13 +232,13 @@ namespace WixToolset.Core
239 232
240 if (String.IsNullOrEmpty(name)) 233 if (String.IsNullOrEmpty(name))
241 { 234 {
242 logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup.log"); 235 logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup:.log");
243 } 236 }
244 else 237 else
245 { 238 {
246 // Ensure only allowable path characters are in "name" (and change spaces to underscores). 239 // Ensure only allowable path characters are in "name" (and change spaces to underscores).
247 fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), "_"); 240 fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), "_");
248 logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ".log"); 241 logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":.log");
249 } 242 }
250 243
251 this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; 244 this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name;
@@ -351,6 +344,37 @@ namespace WixToolset.Core
351 344
352 if (!this.Core.EncounteredError) 345 if (!this.Core.EncounteredError)
353 { 346 {
347 var tuple = new WixBundleTuple(sourceLineNumbers)
348 {
349 UpgradeCode = upgradeCode,
350 Version = version,
351 Copyright = copyright,
352 Name = name,
353 Manufacturer = manufacturer,
354 Attributes = attributes,
355 AboutUrl = aboutUrl,
356 HelpUrl = helpUrl,
357 HelpTelephone = helpTelephone,
358 UpdateUrl = updateUrl,
359 Compressed = YesNoDefaultType.Yes == compressed ? true : YesNoDefaultType.No == compressed ? (bool?)false : null,
360 IconSourceFile = iconSourceFile,
361 SplashScreenSourceFile = splashScreenSourceFile,
362 Condition = condition,
363 Tag = tag,
364 Platform = this.CurrentPlatform,
365 ParentName = parentName,
366 };
367
368 if (!String.IsNullOrEmpty(logVariablePrefixAndExtension))
369 {
370 var split = logVariablePrefixAndExtension.Split(':');
371 tuple.LogPathVariable = split[0];
372 tuple.LogPrefix = split[1];
373 tuple.LogExtension = split[2];
374 }
375
376 this.Core.AddTuple(tuple);;
377
354 if (null != upgradeCode) 378 if (null != upgradeCode)
355 { 379 {
356 this.Core.AddTuple(new WixRelatedBundleTuple(sourceLineNumbers) 380 this.Core.AddTuple(new WixRelatedBundleTuple(sourceLineNumbers)
@@ -360,64 +384,32 @@ namespace WixToolset.Core
360 }); 384 });
361 } 385 }
362 386
363 this.Core.AddTuple(new WixBundleContainerTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BurnDefaultAttachedContainerId)) 387 this.Core.AddTuple(new WixBundleContainerTuple(sourceLineNumbers, Compiler.BurnDefaultAttachedContainerId)
364 { 388 {
365 Name = "bundle-attached.cab", 389 Name = "bundle-attached.cab",
366 Type = ContainerType.Attached 390 Type = ContainerType.Attached
367 }); 391 });
368 392
369 var bundleTuple = this.Core.CreateTuple(sourceLineNumbers, TupleDefinitionType.WixBundle);
370 bundleTuple.Set(0, version);
371 bundleTuple.Set(1, copyright);
372 bundleTuple.Set(2, name);
373 bundleTuple.Set(3, aboutUrl);
374 if (-1 != disableModify)
375 {
376 bundleTuple.Set(4, disableModify);
377 }
378 if (YesNoType.NotSet != disableRemove)
379 {
380 bundleTuple.Set(5, (YesNoType.Yes == disableRemove) ? 1 : 0);
381 }
382 // row.Set(6] - (deprecated) "disable repair"
383 bundleTuple.Set(7, helpTelephone);
384 bundleTuple.Set(8, helpUrl);
385 bundleTuple.Set(9, manufacturer);
386 bundleTuple.Set(10, updateUrl);
387 if (YesNoDefaultType.Default != compressed)
388 {
389 bundleTuple.Set(11, (YesNoDefaultType.Yes == compressed) ? 1 : 0);
390 }
391
392 bundleTuple.Set(12, logVariablePrefixAndExtension);
393 bundleTuple.Set(13, iconSourceFile);
394 bundleTuple.Set(14, splashScreenSourceFile);
395 bundleTuple.Set(15, condition);
396 bundleTuple.Set(16, tag);
397 bundleTuple.Set(17, this.CurrentPlatform.ToString());
398 bundleTuple.Set(18, parentName);
399 bundleTuple.Set(19, upgradeCode);
400
401 // Ensure that the bundle stores the well-known persisted values. 393 // Ensure that the bundle stores the well-known persisted values.
402 this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BURN_BUNDLE_NAME)) 394 this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, BurnConstants.BURN_BUNDLE_NAME))
403 { 395 {
404 Hidden = false, 396 Hidden = false,
405 Persisted = true 397 Persisted = true
406 }); 398 });
407 399
408 this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BURN_BUNDLE_ORIGINAL_SOURCE)) 400 this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE))
409 { 401 {
410 Hidden = false, 402 Hidden = false,
411 Persisted = true 403 Persisted = true
412 }); 404 });
413 405
414 this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER)) 406 this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER))
415 { 407 {
416 Hidden = false, 408 Hidden = false,
417 Persisted = true 409 Persisted = true
418 }); 410 });
419 411
420 this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BURN_BUNDLE_LAST_USED_SOURCE)) 412 this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, BurnConstants.BURN_BUNDLE_LAST_USED_SOURCE))
421 { 413 {
422 Hidden = false, 414 Hidden = false,
423 Persisted = true 415 Persisted = true
@@ -473,7 +465,7 @@ namespace WixToolset.Core
473 465
474 this.Core.ParseForExtensionElements(node); 466 this.Core.ParseForExtensionElements(node);
475 467
476 return YesNoType.Yes == disableLog ? null : String.Concat(variable, ":", logPrefix, logExtension); 468 return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension);
477 } 469 }
478 470
479 /// <summary> 471 /// <summary>
@@ -1126,9 +1118,9 @@ namespace WixToolset.Core
1126 tuple = new WixBundlePayloadTuple(sourceLineNumbers, id) 1118 tuple = new WixBundlePayloadTuple(sourceLineNumbers, id)
1127 { 1119 {
1128 Name = String.IsNullOrEmpty(name) ? Path.GetFileName(sourceFile) : name, 1120 Name = String.IsNullOrEmpty(name) ? Path.GetFileName(sourceFile) : name,
1129 SourceFile = sourceFile, 1121 SourceFile = new IntermediateFieldPathValue { Path = sourceFile },
1130 DownloadUrl = downloadUrl, 1122 DownloadUrl = downloadUrl,
1131 Compressed = compressed, 1123 Compressed = (compressed == YesNoDefaultType.Yes) ? true : (compressed == YesNoDefaultType.No) ? (bool?)false : null,
1132 UnresolvedSourceFile = sourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. 1124 UnresolvedSourceFile = sourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding.
1133 DisplayName = displayName, 1125 DisplayName = displayName,
1134 Description = description, 1126 Description = description,
@@ -1665,14 +1657,12 @@ namespace WixToolset.Core
1665 var vital = YesNoType.Yes; 1657 var vital = YesNoType.Yes;
1666 string installCommand = null; 1658 string installCommand = null;
1667 string repairCommand = null; 1659 string repairCommand = null;
1668 var repairable = YesNoType.NotSet;
1669 string uninstallCommand = null; 1660 string uninstallCommand = null;
1670 var perMachine = YesNoDefaultType.NotSet; 1661 var perMachine = YesNoDefaultType.NotSet;
1671 string detectCondition = null; 1662 string detectCondition = null;
1672 string protocol = null; 1663 string protocol = null;
1673 var installSize = CompilerConstants.IntegerNotSet; 1664 var installSize = CompilerConstants.IntegerNotSet;
1674 string msuKB = null; 1665 string msuKB = null;
1675 var suppressLooseFilePayloadGeneration = YesNoType.NotSet;
1676 var enableSignatureVerification = YesNoType.No; 1666 var enableSignatureVerification = YesNoType.No;
1677 var compressed = YesNoDefaultType.Default; 1667 var compressed = YesNoDefaultType.Default;
1678 var displayInternalUI = YesNoType.NotSet; 1668 var displayInternalUI = YesNoType.NotSet;
@@ -1779,7 +1769,6 @@ namespace WixToolset.Core
1779 break; 1769 break;
1780 case "RepairCommand": 1770 case "RepairCommand":
1781 repairCommand = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); 1771 repairCommand = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
1782 repairable = YesNoType.Yes;
1783 allowed = (packageType == WixBundlePackageType.Exe); 1772 allowed = (packageType == WixBundlePackageType.Exe);
1784 break; 1773 break;
1785 case "UninstallCommand": 1774 case "UninstallCommand":
@@ -1808,11 +1797,6 @@ namespace WixToolset.Core
1808 case "Compressed": 1797 case "Compressed":
1809 compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); 1798 compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
1810 break; 1799 break;
1811 case "SuppressLooseFilePayloadGeneration":
1812 this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
1813 suppressLooseFilePayloadGeneration = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1814 allowed = (packageType == WixBundlePackageType.Msi);
1815 break;
1816 case "EnableSignatureVerification": 1800 case "EnableSignatureVerification":
1817 enableSignatureVerification = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); 1801 enableSignatureVerification = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1818 break; 1802 break;
@@ -2076,7 +2060,7 @@ namespace WixToolset.Core
2076 case WixBundlePackageType.Exe: 2060 case WixBundlePackageType.Exe:
2077 this.Core.AddTuple(new WixBundleExePackageTuple(sourceLineNumbers, id) 2061 this.Core.AddTuple(new WixBundleExePackageTuple(sourceLineNumbers, id)
2078 { 2062 {
2079 Attributes = (YesNoType.Yes == repairable) ? WixBundleExePackageAttributes.Repairable : 0, 2063 Attributes = WixBundleExePackageAttributes.None,
2080 DetectCondition = detectCondition, 2064 DetectCondition = detectCondition,
2081 InstallCommand = installCommand, 2065 InstallCommand = installCommand,
2082 RepairCommand = repairCommand, 2066 RepairCommand = repairCommand,
@@ -2090,7 +2074,6 @@ namespace WixToolset.Core
2090 msiAttributes |= (YesNoType.Yes == displayInternalUI) ? WixBundleMsiPackageAttributes.DisplayInternalUI : 0; 2074 msiAttributes |= (YesNoType.Yes == displayInternalUI) ? WixBundleMsiPackageAttributes.DisplayInternalUI : 0;
2091 msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0; 2075 msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0;
2092 msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0; 2076 msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0;
2093 msiAttributes |= (YesNoType.Yes == suppressLooseFilePayloadGeneration) ? WixBundleMsiPackageAttributes.SuppressLooseFilePayloadGeneration : 0;
2094 2077
2095 this.Core.AddTuple(new WixBundleMsiPackageTuple(sourceLineNumbers, id) 2078 this.Core.AddTuple(new WixBundleMsiPackageTuple(sourceLineNumbers, id)
2096 { 2079 {
@@ -2458,9 +2441,9 @@ namespace WixToolset.Core
2458 2441
2459 if (!this.Core.EncounteredError) 2442 if (!this.Core.EncounteredError)
2460 { 2443 {
2461 var tuple = new WixBundleMsiPropertyTuple(sourceLineNumbers) 2444 var tuple = new WixBundleMsiPropertyTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, packageId, name))
2462 { 2445 {
2463 WixBundlePackageRef = packageId, 2446 PackageRef = packageId,
2464 Name = name, 2447 Name = name,
2465 Value = value 2448 Value = value
2466 }; 2449 };
@@ -2514,10 +2497,10 @@ namespace WixToolset.Core
2514 2497
2515 if (!this.Core.EncounteredError) 2498 if (!this.Core.EncounteredError)
2516 { 2499 {
2517 this.Core.AddTuple(new WixBundleSlipstreamMspTuple(sourceLineNumbers) 2500 this.Core.AddTuple(new WixBundleSlipstreamMspTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, packageId, id))
2518 { 2501 {
2519 WixBundlePackageRef = packageId, 2502 TargetPackageRef = packageId,
2520 MspWixBundlePackageRef = id 2503 MspPackageRef = id
2521 }); 2504 });
2522 } 2505 }
2523 } 2506 }
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. 1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 2
3namespace WixToolset.Core.ExtensibilityServices 3namespace WixToolset.Core.ExtensibilityServices
4{ 4{
@@ -40,6 +40,15 @@ namespace WixToolset.Core.ExtensibilityServices
40 return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); 40 return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant();
41 } 41 }
42 42
43 public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name)
44 {
45 return new ResolvedDirectory
46 {
47 DirectoryParent = directoryParent,
48 Name = name
49 };
50 }
51
43 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) 52 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null)
44 { 53 {
45 return new TrackedFile(path, type, sourceLineNumbers); 54 return new TrackedFile(path, type, sourceLineNumbers);
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs b/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs
index 6dc18271..15cd4fc9 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs
+++ b/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs
@@ -1,24 +1,18 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. 1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 2
3namespace WixToolset.Core.WindowsInstaller.Bind 3namespace WixToolset.Core.ExtensibilityServices
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using WixToolset.Data; 8 using WixToolset.Data;
9 using WixToolset.Data.WindowsInstaller; 9 using WixToolset.Data.WindowsInstaller;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
10 12
11 internal static class PathResolver 13 internal class PathResolver : IPathResolver
12 { 14 {
13 /// <summary> 15 public string GetDirectoryPath(Dictionary<string, IResolvedDirectory> directories, Dictionary<string, string> componentIdGenSeeds, string directory, bool canonicalize)
14 /// Get the source path of a directory.
15 /// </summary>
16 /// <param name="directories">All cached directories.</param>
17 /// <param name="componentIdGenSeeds">Hash table of Component GUID generation seeds indexed by directory id.</param>
18 /// <param name="directory">Directory identifier.</param>
19 /// <param name="canonicalize">Canonicalize the path for standard directories.</param>
20 /// <returns>Source path of a directory.</returns>
21 public static string GetDirectoryPath(Dictionary<string, ResolvedDirectory> directories, Dictionary<string, string> componentIdGenSeeds, string directory, bool canonicalize)
22 { 16 {
23 if (!directories.TryGetValue(directory, out var resolvedDirectory)) 17 if (!directories.TryGetValue(directory, out var resolvedDirectory))
24 { 18 {
@@ -51,7 +45,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
51 } 45 }
52 else 46 else
53 { 47 {
54 string parentPath = GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize); 48 var parentPath = this.GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize);
55 49
56 if (null != resolvedDirectory.Name) 50 if (null != resolvedDirectory.Name)
57 { 51 {
@@ -68,18 +62,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
68 return resolvedDirectory.Path; 62 return resolvedDirectory.Path;
69 } 63 }
70 64
71 /// <summary> 65 public string GetFileSourcePath(Dictionary<string, IResolvedDirectory> directories, string directoryId, string fileName, bool compressed, bool useLongName)
72 /// Gets the source path of a file.
73 /// </summary>
74 /// <param name="directories">All cached directories in <see cref="ResolvedDirectory"/>.</param>
75 /// <param name="directoryId">Parent directory identifier.</param>
76 /// <param name="fileName">File name (in long|source format).</param>
77 /// <param name="compressed">Specifies the package is compressed.</param>
78 /// <param name="useLongName">Specifies the package uses long file names.</param>
79 /// <returns>Source path of file relative to package directory.</returns>
80 public static string GetFileSourcePath(Dictionary<string, ResolvedDirectory> directories, string directoryId, string fileName, bool compressed, bool useLongName)
81 { 66 {
82 string fileSourcePath = Common.GetName(fileName, true, useLongName); 67 var fileSourcePath = Common.GetName(fileName, true, useLongName);
83 68
84 if (compressed) 69 if (compressed)
85 { 70 {
@@ -90,7 +75,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
90 { 75 {
91 // Get the relative path of where we want the file to be layed out as specified 76 // Get the relative path of where we want the file to be layed out as specified
92 // in the Directory table. 77 // in the Directory table.
93 string directoryPath = PathResolver.GetDirectoryPath(directories, null, directoryId, false); 78 var directoryPath = this.GetDirectoryPath(directories, null, directoryId, false);
94 fileSourcePath = Path.Combine(directoryPath, fileSourcePath); 79 fileSourcePath = Path.Combine(directoryPath, fileSourcePath);
95 } 80 }
96 81
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using WixToolset.Extensibility.Data;
6
7 internal class ResolvedDirectory : IResolvedDirectory
8 {
9 public string DirectoryParent { get; set; }
10
11 public string Name { get; set; }
12
13 public string Path { get; set; }
14 }
15}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. 1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 2
3namespace WixToolset.Core.ExtensibilityServices 3namespace WixToolset.Core.ExtensibilityServices
4{ 4{
5 using System;
6 using System.Linq; 5 using System.Linq;
7 using WixToolset.Data; 6 using WixToolset.Data;
8 using WixToolset.Data.WindowsInstaller; 7 using WixToolset.Data.WindowsInstaller;
@@ -10,14 +9,9 @@ namespace WixToolset.Core.ExtensibilityServices
10 9
11 internal class WindowsInstallerBackendHelper : IWindowsInstallerBackendHelper 10 internal class WindowsInstallerBackendHelper : IWindowsInstallerBackendHelper
12 { 11 {
13 public WindowsInstallerBackendHelper(IServiceProvider serviceProvider) 12 public bool TryAddTupleToOutputMatchingTableDefinitions(IntermediateTuple tuple, Output output, TableDefinition[] tableDefinitions) => this.TryAddTupleToOutputMatchingTableDefinitions(tuple, output, tableDefinitions, false);
14 {
15 this.ServiceProvider = serviceProvider;
16 }
17 13
18 private IServiceProvider ServiceProvider { get; } 14 public bool TryAddTupleToOutputMatchingTableDefinitions(IntermediateTuple tuple, Output output, TableDefinition[] tableDefinitions, bool columnZeroIsId)
19
20 public bool TryAddTupleToOutputMatchingTableDefinitions(IntermediateTuple tuple, Output output, TableDefinition[] tableDefinitions)
21 { 15 {
22 var tableDefinition = tableDefinitions.FirstOrDefault(t => t.Name == tuple.Definition.Name); 16 var tableDefinition = tableDefinitions.FirstOrDefault(t => t.Name == tuple.Definition.Name);
23 17
@@ -28,6 +22,14 @@ namespace WixToolset.Core.ExtensibilityServices
28 22
29 var table = output.EnsureTable(tableDefinition); 23 var table = output.EnsureTable(tableDefinition);
30 var row = table.CreateRow(tuple.SourceLineNumbers); 24 var row = table.CreateRow(tuple.SourceLineNumbers);
25 var rowOffset = 0;
26
27 if (columnZeroIsId)
28 {
29 row[0] = tuple.Id.Id;
30 rowOffset = 1;
31 }
32
31 for (var i = 0; i < tuple.Fields.Length; ++i) 33 for (var i = 0; i < tuple.Fields.Length; ++i)
32 { 34 {
33 if (i < tableDefinition.Columns.Length) 35 if (i < tableDefinition.Columns.Length)
@@ -36,13 +38,13 @@ namespace WixToolset.Core.ExtensibilityServices
36 38
37 switch (column.Type) 39 switch (column.Type)
38 { 40 {
39 case ColumnType.Number: 41 case ColumnType.Number:
40 row[i] = tuple.AsNumber(i); 42 row[i + rowOffset] = column.Nullable ? tuple.AsNullableNumber(i) : tuple.AsNumber(i);
41 break; 43 break;
42 44
43 default: 45 default:
44 row[i] = tuple.AsString(i); 46 row[i + rowOffset] = tuple.AsString(i);
45 break; 47 break;
46 } 48 }
47 } 49 }
48 } 50 }
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
12 using WixToolset.Data; 12 using WixToolset.Data;
13 using WixToolset.Data.Tuples; 13 using WixToolset.Data.Tuples;
14 using WixToolset.Extensibility.Services; 14 using WixToolset.Extensibility.Services;
15 using WixToolset.Data.Burn;
15 16
16 /// <summary> 17 /// <summary>
17 /// Grouping and Ordering class of the WiX toolset. 18 /// Grouping and Ordering class of the WiX toolset.
18 /// </summary> 19 /// </summary>
19 internal class WixGroupingOrdering 20 internal class WixGroupingOrdering
20 { 21 {
21 private readonly IMessaging messageHandler; 22 private readonly IMessaging Messaging;
22 private List<string> groupTypes; 23 private List<string> groupTypes;
23 private List<string> itemTypes; 24 private List<string> itemTypes;
24 private ItemCollection items; 25 private ItemCollection items;
25 private readonly List<int> rowsUsed; 26 private readonly List<int> rowsUsed;
26 private bool loaded; 27 private bool loaded;
27 private bool encounteredError;
28 28
29 /// <summary> 29 /// <summary>
30 /// Creates a WixGroupingOrdering object. 30 /// Creates a WixGroupingOrdering object.
@@ -36,11 +36,10 @@ namespace WixToolset.Core.Link
36 public WixGroupingOrdering(IntermediateSection entrySections, IMessaging messageHandler) 36 public WixGroupingOrdering(IntermediateSection entrySections, IMessaging messageHandler)
37 { 37 {
38 this.EntrySection = entrySections; 38 this.EntrySection = entrySections;
39 this.messageHandler = messageHandler; 39 this.Messaging = messageHandler;
40 40
41 this.rowsUsed = new List<int>(); 41 this.rowsUsed = new List<int>();
42 this.loaded = false; 42 this.loaded = false;
43 this.encounteredError = false;
44 } 43 }
45 44
46 private IntermediateSection EntrySection { get; } 45 private IntermediateSection EntrySection { get; }
@@ -71,7 +70,7 @@ namespace WixToolset.Core.Link
71 Debug.Assert(this.groupTypes.Contains(parentTypeString)); 70 Debug.Assert(this.groupTypes.Contains(parentTypeString));
72 71
73 this.CreateOrderedList(parentTypeString, parentId, out var orderedItems); 72 this.CreateOrderedList(parentTypeString, parentId, out var orderedItems);
74 if (this.encounteredError) 73 if (this.Messaging.EncounteredError)
75 { 74 {
76 return; 75 return;
77 } 76 }
@@ -95,7 +94,7 @@ namespace WixToolset.Core.Link
95 Debug.Assert(this.groupTypes.Contains(parentTypeString)); 94 Debug.Assert(this.groupTypes.Contains(parentTypeString));
96 95
97 this.LoadFlattenOrderGroups(); 96 this.LoadFlattenOrderGroups();
98 if (this.encounteredError) 97 if (this.Messaging.EncounteredError)
99 { 98 {
100 return; 99 return;
101 } 100 }
@@ -127,14 +126,14 @@ namespace WixToolset.Core.Link
127 orderedItems = null; 126 orderedItems = null;
128 127
129 this.LoadFlattenOrderGroups(); 128 this.LoadFlattenOrderGroups();
130 if (this.encounteredError) 129 if (this.Messaging.EncounteredError)
131 { 130 {
132 return; 131 return;
133 } 132 }
134 133
135 if (!this.items.TryGetValue(parentType, parentId, out var parentItem)) 134 if (!this.items.TryGetValue(parentType, parentId, out var parentItem))
136 { 135 {
137 this.messageHandler.Write(ErrorMessages.IdentifierNotFound(parentType, parentId)); 136 this.Messaging.Write(ErrorMessages.IdentifierNotFound(parentType, parentId));
138 return; 137 return;
139 } 138 }
140 139
@@ -216,7 +215,7 @@ namespace WixToolset.Core.Link
216 // dependencies. Group references, however, we can check directly. 215 // dependencies. Group references, however, we can check directly.
217 this.FindCircularGroupReferences(); 216 this.FindCircularGroupReferences();
218 217
219 if (!this.encounteredError) 218 if (!this.Messaging.EncounteredError)
220 { 219 {
221 this.FlattenGroups(); 220 this.FlattenGroups();
222 this.FlattenOrdering(); 221 this.FlattenOrdering();
@@ -304,7 +303,7 @@ namespace WixToolset.Core.Link
304 if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference)) 303 if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference))
305 { 304 {
306 itemsInKnownLoops.Add(itemsSeen); 305 itemsInKnownLoops.Add(itemsSeen);
307 this.messageHandler.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); 306 this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference));
308 } 307 }
309 } 308 }
310 } 309 }
@@ -376,12 +375,12 @@ namespace WixToolset.Core.Link
376 375
377 if (!this.items.TryGetValue(rowItemType, rowItemName, out var item)) 376 if (!this.items.TryGetValue(rowItemType, rowItemName, out var item))
378 { 377 {
379 this.messageHandler.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName)); 378 this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName));
380 } 379 }
381 380
382 if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out var dependsOn)) 381 if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out var dependsOn))
383 { 382 {
384 this.messageHandler.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); 383 this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName));
385 } 384 }
386 385
387 if (null == item || null == dependsOn) 386 if (null == item || null == dependsOn)
@@ -389,7 +388,7 @@ namespace WixToolset.Core.Link
389 continue; 388 continue;
390 } 389 }
391 390
392 item.AddAfter(dependsOn, this.messageHandler); 391 item.AddAfter(dependsOn, this.Messaging);
393 } 392 }
394 } 393 }
395 394
@@ -404,12 +403,12 @@ namespace WixToolset.Core.Link
404 // ordering. 403 // ordering.
405 foreach (Item item in this.items) 404 foreach (Item item in this.items)
406 { 405 {
407 item.PropagateAfterToChildItems(this.messageHandler); 406 item.PropagateAfterToChildItems(this.Messaging);
408 } 407 }
409 408
410 foreach (Item item in this.items) 409 foreach (Item item in this.items)
411 { 410 {
412 item.FlattenAfters(this.messageHandler); 411 item.FlattenAfters(this.Messaging);
413 } 412 }
414 } 413 }
415 414
@@ -668,7 +667,7 @@ namespace WixToolset.Core.Link
668 { 667 {
669 if (String.Equals(nameof(ComplexReferenceChildType.Package), this.Type, StringComparison.Ordinal) || 668 if (String.Equals(nameof(ComplexReferenceChildType.Package), this.Type, StringComparison.Ordinal) ||
670 (String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal) && 669 (String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal) &&
671 !String.Equals(Compiler.BurnUXContainerId.Id, this.Id, StringComparison.Ordinal))) 670 !String.Equals(BurnConstants.BurnUXContainerName, this.Id, StringComparison.Ordinal)))
672 { 671 {
673 return false; 672 return false;
674 } 673 }
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
24 this.AddService((provider, singletons) => AddSingleton<IParseHelper>(singletons, new ParseHelper(provider))); 24 this.AddService((provider, singletons) => AddSingleton<IParseHelper>(singletons, new ParseHelper(provider)));
25 this.AddService((provider, singletons) => AddSingleton<IPreprocessHelper>(singletons, new PreprocessHelper(provider))); 25 this.AddService((provider, singletons) => AddSingleton<IPreprocessHelper>(singletons, new PreprocessHelper(provider)));
26 this.AddService((provider, singletons) => AddSingleton<IBackendHelper>(singletons, new BackendHelper(provider))); 26 this.AddService((provider, singletons) => AddSingleton<IBackendHelper>(singletons, new BackendHelper(provider)));
27 this.AddService((provider, singletons) => AddSingleton<IWindowsInstallerBackendHelper>(singletons, new WindowsInstallerBackendHelper(provider))); 27 this.AddService((provider, singletons) => AddSingleton<IPathResolver>(singletons, new PathResolver()));
28 this.AddService((provider, singletons) => AddSingleton<IWindowsInstallerBackendHelper>(singletons, new WindowsInstallerBackendHelper()));
28 29
29 // Transients. 30 // Transients.
30 this.AddService<ICommandLineArguments>((provider, singletons) => new CommandLineArguments(provider)); 31 this.AddService<ICommandLineArguments>((provider, singletons) => new CommandLineArguments(provider));
@@ -47,6 +48,7 @@ namespace WixToolset.Core
47 this.AddService<IDecompileResult>((provider, singletons) => new DecompileResult()); 48 this.AddService<IDecompileResult>((provider, singletons) => new DecompileResult());
48 this.AddService<IIncludedFile>((provider, singletons) => new IncludedFile()); 49 this.AddService<IIncludedFile>((provider, singletons) => new IncludedFile());
49 this.AddService<IPreprocessResult>((provider, singletons) => new PreprocessResult()); 50 this.AddService<IPreprocessResult>((provider, singletons) => new PreprocessResult());
51 this.AddService<IResolvedDirectory>((provider, singletons) => new ResolvedDirectory());
50 this.AddService<IResolveFileResult>((provider, singletons) => new ResolveFileResult()); 52 this.AddService<IResolveFileResult>((provider, singletons) => new ResolveFileResult());
51 this.AddService<IResolveResult>((provider, singletons) => new ResolveResult()); 53 this.AddService<IResolveResult>((provider, singletons) => new ResolveResult());
52 this.AddService<IResolvedCabinet>((provider, singletons) => new ResolvedCabinet()); 54 this.AddService<IResolvedCabinet>((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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using WixToolset.Data;
10 using WixToolset.Data.Tuples;
11 using Xunit;
12
13 public class BundleFixture
14 {
15 [Fact]
16 public void CanBuildSimpleBundle()
17 {
18 var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe");
19 var folder = TestData.Get(@"TestData\SimpleBundle");
20
21 using (var fs = new DisposableFileSystem())
22 {
23 var baseFolder = fs.GetFolder();
24 var intermediateFolder = Path.Combine(baseFolder, "obj");
25
26 var result = WixRunner.Execute(new[]
27 {
28 "build",
29 Path.Combine(folder, "Bundle.wxs"),
30 "-loc", Path.Combine(folder, "Bundle.en-us.wxl"),
31 "-bindpath", Path.Combine(folder, "data"),
32 "-intermediateFolder", intermediateFolder,
33 "-burnStub", burnStubPath,
34 "-o", Path.Combine(baseFolder, @"bin\test.exe")
35 });
36
37 result.AssertSuccess();
38
39 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe")));
40#if TODO
41 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
42#endif
43
44 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir"));
45 var section = intermediate.Sections.Single();
46
47 var msiTuple = section.Tuples.OfType<WixBundlePackageTuple>().Single();
48 Assert.Equal("test.msi", msiTuple.Id.Id );
49 }
50 }
51 }
52}
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
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="BundleName">~TestBundle</String>
9
10</WixLocalization>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Bundle Name="!(loc.BundleName)" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
4 <BootstrapperApplication SourceFile="fakeba.dll" />
5 <Chain>
6 <MsiPackage SourceFile="test.msi">
7 <MsiProperty Name="TEST" Value="1" />
8 </MsiPackage>
9 </Chain>
10 </Bundle>
11</Wix>
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
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi
Binary files 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 @@
13 </PropertyGroup> 13 </PropertyGroup>
14 14
15 <ItemGroup> 15 <ItemGroup>
16 <Content Include="TestData\.Data\burn.exe" CopyToOutputDirectory="PreserveNewest" />
16 <Content Include="TestData\AppSearch\ComponentSearch.wxs" CopyToOutputDirectory="PreserveNewest" /> 17 <Content Include="TestData\AppSearch\ComponentSearch.wxs" CopyToOutputDirectory="PreserveNewest" />
17 <Content Include="TestData\AppSearch\DirectorySearch.wxs" CopyToOutputDirectory="PreserveNewest" /> 18 <Content Include="TestData\AppSearch\DirectorySearch.wxs" CopyToOutputDirectory="PreserveNewest" />
18 <Content Include="TestData\AppSearch\FileSearch.wxs" CopyToOutputDirectory="PreserveNewest" /> 19 <Content Include="TestData\AppSearch\FileSearch.wxs" CopyToOutputDirectory="PreserveNewest" />
@@ -31,6 +32,9 @@
31 <Content Include="TestData\ReserveCost\ReserveCost.wxs" CopyToOutputDirectory="PreserveNewest" /> 32 <Content Include="TestData\ReserveCost\ReserveCost.wxs" CopyToOutputDirectory="PreserveNewest" />
32 <Content Include="TestData\ServiceInstall\OwnProcess.wxs" CopyToOutputDirectory="PreserveNewest" /> 33 <Content Include="TestData\ServiceInstall\OwnProcess.wxs" CopyToOutputDirectory="PreserveNewest" />
33 <Content Include="TestData\Shortcut\ShortcutProperty.wxs" CopyToOutputDirectory="PreserveNewest" /> 34 <Content Include="TestData\Shortcut\ShortcutProperty.wxs" CopyToOutputDirectory="PreserveNewest" />
35 <Content Include="TestData\SimpleBundle\data\fakeba.dll" CopyToOutputDirectory="PreserveNewest" />
36 <Content Include="TestData\SimpleBundle\data\MsiPackage\Shared.dll" CopyToOutputDirectory="PreserveNewest" />
37 <Content Include="TestData\SimpleBundle\data\MsiPackage\test.txt" CopyToOutputDirectory="PreserveNewest" />
34 <Content Include="TestData\SimpleModule\data\test.txt" CopyToOutputDirectory="PreserveNewest" /> 38 <Content Include="TestData\SimpleModule\data\test.txt" CopyToOutputDirectory="PreserveNewest" />
35 <Content Include="TestData\SimpleModule\Module.en-us.wxl" CopyToOutputDirectory="PreserveNewest" /> 39 <Content Include="TestData\SimpleModule\Module.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
36 <Content Include="TestData\SimpleModule\Module.wxs" CopyToOutputDirectory="PreserveNewest" /> 40 <Content Include="TestData\SimpleModule\Module.wxs" CopyToOutputDirectory="PreserveNewest" />
@@ -66,6 +70,9 @@
66 <Content Include="TestData\SetProperty\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" /> 70 <Content Include="TestData\SetProperty\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
67 <Content Include="TestData\SetProperty\Package.wxs" CopyToOutputDirectory="PreserveNewest" /> 71 <Content Include="TestData\SetProperty\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
68 <Content Include="TestData\SetProperty\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" /> 72 <Content Include="TestData\SetProperty\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
73 <Content Include="TestData\SimpleBundle\data\test.msi" CopyToOutputDirectory="PreserveNewest" />
74 <Content Include="TestData\SimpleBundle\Bundle.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
75 <Content Include="TestData\SimpleBundle\Bundle.wxs" CopyToOutputDirectory="PreserveNewest" />
69 <Content Include="TestData\SingleFile\data\test.txt" CopyToOutputDirectory="PreserveNewest" /> 76 <Content Include="TestData\SingleFile\data\test.txt" CopyToOutputDirectory="PreserveNewest" />
70 <Content Include="TestData\SingleFile\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" /> 77 <Content Include="TestData\SingleFile\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
71 <Content Include="TestData\SingleFile\Package.wxs" CopyToOutputDirectory="PreserveNewest" /> 78 <Content Include="TestData\SingleFile\Package.wxs" CopyToOutputDirectory="PreserveNewest" />