aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-09-03 16:08:33 -0700
committerRob Mensching <rob@firegiant.com>2022-09-23 09:50:52 -0700
commit63b588262aa0b7869741aa888905c1eeda7ae2f8 (patch)
tree54ec7ccb6e1a6f4859d3d2f0bbd8c79cff50b1ed
parentdb2fe6554f449a108b77cd3d2c7f30a68d54ddc2 (diff)
downloadwix-63b588262aa0b7869741aa888905c1eeda7ae2f8.tar.gz
wix-63b588262aa0b7869741aa888905c1eeda7ae2f8.tar.bz2
wix-63b588262aa0b7869741aa888905c1eeda7ae2f8.zip
Implement single pass patch build
This new implementation of patching in WiX v4 creates an MSP's transforms and MSP file in a single pass. This single pass allows the build to use MSI as the source of files for diffing purposes. Completes 6401 Fixes 4629
-rw-r--r--src/api/wix/WixToolset.Data/ErrorMessages.cs3
-rw-r--r--src/api/wix/WixToolset.Data/Symbols/WixPatchBaselineSymbol.cs8
-rw-r--r--src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs5
-rw-r--r--src/api/wix/WixToolset.Extensibility/Data/IFileFacade.cs48
-rw-r--r--src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileContext.cs5
-rw-r--r--src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs24
-rw-r--r--src/api/wix/WixToolset.Extensibility/Services/IFileResolver.cs38
-rw-r--r--src/api/wix/WixToolset.Extensibility/Services/IWindowsInstallerBackendHelper.cs17
-rw-r--r--src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs15
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs2
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs33
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs199
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs24
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs10
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs17
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs10
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs100
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs5
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/DataLoader.cs49
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs10
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs61
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs7
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs15
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs14
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs22
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs210
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateSymbolsWithFileFacadesCommand.cs73
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs331
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs83
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Differ.cs38
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/FileFacade.cs82
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs11
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs4
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs96
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs85
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs415
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs72
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs14
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs74
-rw-r--r--src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs5
-rw-r--r--src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs235
-rw-r--r--src/wix/WixToolset.Core/BindContext.cs2
-rw-r--r--src/wix/WixToolset.Core/CommandLine/BuildCommand.cs25
-rw-r--r--src/wix/WixToolset.Core/Compiler.cs27
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs17
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs175
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/FileResolver.cs (renamed from src/wix/WixToolset.Core/Bind/FileResolver.cs)98
-rw-r--r--src/wix/WixToolset.Core/Librarian.cs8
-rw-r--r--src/wix/WixToolset.Core/Resolver.cs41
-rw-r--r--src/wix/WixToolset.Core/WixToolsetServiceProvider.cs1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs180
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.baseline-data/A.txt2
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data-alternative/A.txt3
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data/A.txt4
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchUsingAdminImages/Patch.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchWithFileChangesUsingMsi/Patch.wxs16
56 files changed, 1508 insertions, 1676 deletions
diff --git a/src/api/wix/WixToolset.Data/ErrorMessages.cs b/src/api/wix/WixToolset.Data/ErrorMessages.cs
index 40378a2e..77ce73aa 100644
--- a/src/api/wix/WixToolset.Data/ErrorMessages.cs
+++ b/src/api/wix/WixToolset.Data/ErrorMessages.cs
@@ -651,7 +651,8 @@ namespace WixToolset.Data
651 public static Message FileNotFound(SourceLineNumber sourceLineNumbers, string file, string fileType, IEnumerable<string> checkedPaths) 651 public static Message FileNotFound(SourceLineNumber sourceLineNumbers, string file, string fileType, IEnumerable<string> checkedPaths)
652 { 652 {
653 var combinedCheckedPaths = String.Join(", ", checkedPaths); 653 var combinedCheckedPaths = String.Join(", ", checkedPaths);
654 return Message(sourceLineNumbers, Ids.FileNotFound, "The system cannot find the file '{0}' with type '{1}'. The following paths were checked: {2}", file, fileType, combinedCheckedPaths); 654 var withType = String.IsNullOrEmpty(fileType) ? String.Empty : $" with type '{fileType}'";
655 return Message(sourceLineNumbers, Ids.FileNotFound, "The system cannot find the file '{0}'{1}. The following paths were checked: {2}", file, withType, combinedCheckedPaths);
655 } 656 }
656 657
657 public static Message FileOrDirectoryPathRequired(string parameter) 658 public static Message FileOrDirectoryPathRequired(string parameter)
diff --git a/src/api/wix/WixToolset.Data/Symbols/WixPatchBaselineSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixPatchBaselineSymbol.cs
index d7295424..cbf51e2e 100644
--- a/src/api/wix/WixToolset.Data/Symbols/WixPatchBaselineSymbol.cs
+++ b/src/api/wix/WixToolset.Data/Symbols/WixPatchBaselineSymbol.cs
@@ -14,7 +14,6 @@ namespace WixToolset.Data
14 new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.ValidationFlags), IntermediateFieldType.Number), 14 new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.ValidationFlags), IntermediateFieldType.Number),
15 new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.BaselineFile), IntermediateFieldType.Path), 15 new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.BaselineFile), IntermediateFieldType.Path),
16 new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.UpdateFile), IntermediateFieldType.Path), 16 new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.UpdateFile), IntermediateFieldType.Path),
17 new IntermediateFieldDefinition(nameof(WixPatchBaselineSymbolFields.TransformFile), IntermediateFieldType.Path),
18 }, 17 },
19 typeof(WixPatchBaselineSymbol)); 18 typeof(WixPatchBaselineSymbol));
20 } 19 }
@@ -28,7 +27,6 @@ namespace WixToolset.Data.Symbols
28 ValidationFlags, 27 ValidationFlags,
29 BaselineFile, 28 BaselineFile,
30 UpdateFile, 29 UpdateFile,
31 TransformFile,
32 } 30 }
33 31
34 public class WixPatchBaselineSymbol : IntermediateSymbol 32 public class WixPatchBaselineSymbol : IntermediateSymbol
@@ -66,11 +64,5 @@ namespace WixToolset.Data.Symbols
66 get => this.Fields[(int)WixPatchBaselineSymbolFields.UpdateFile].AsPath(); 64 get => this.Fields[(int)WixPatchBaselineSymbolFields.UpdateFile].AsPath();
67 set => this.Set((int)WixPatchBaselineSymbolFields.UpdateFile, value); 65 set => this.Set((int)WixPatchBaselineSymbolFields.UpdateFile, value);
68 } 66 }
69
70 public IntermediateFieldPathValue TransformFile
71 {
72 get => this.Fields[(int)WixPatchBaselineSymbolFields.TransformFile].AsPath();
73 set => this.Set((int)WixPatchBaselineSymbolFields.TransformFile, value);
74 }
75 } 67 }
76} 68}
diff --git a/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs b/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs
index 671da292..9d663c65 100644
--- a/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs
+++ b/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs
@@ -18,6 +18,11 @@ namespace WixToolset.Extensibility.Data
18 IServiceProvider ServiceProvider { get; } 18 IServiceProvider ServiceProvider { get; }
19 19
20 /// <summary> 20 /// <summary>
21 /// Bind paths used during resolution.
22 /// </summary>
23 IReadOnlyCollection<IBindPath> BindPaths { get; set; }
24
25 /// <summary>
21 /// Counnt of threads to use in cabbing. 26 /// Counnt of threads to use in cabbing.
22 /// </summary> 27 /// </summary>
23 int CabbingThreadCount { get; set; } 28 int CabbingThreadCount { get; set; }
diff --git a/src/api/wix/WixToolset.Extensibility/Data/IFileFacade.cs b/src/api/wix/WixToolset.Extensibility/Data/IFileFacade.cs
index fea00d4e..8a9e3fee 100644
--- a/src/api/wix/WixToolset.Extensibility/Data/IFileFacade.cs
+++ b/src/api/wix/WixToolset.Extensibility/Data/IFileFacade.cs
@@ -5,34 +5,13 @@ namespace WixToolset.Extensibility.Data
5 using System.Collections.Generic; 5 using System.Collections.Generic;
6 using WixToolset.Data; 6 using WixToolset.Data;
7 using WixToolset.Data.Symbols; 7 using WixToolset.Data.Symbols;
8 using WixToolset.Data.WindowsInstaller.Rows;
9 8
10 /// <summary> 9 /// <summary>
11 /// Interface that provides a common facade over <c>FileSymbol</c> and <c>FileRow</c>. 10 /// Interface that provides a common facade over file information.
12 /// </summary> 11 /// </summary>
13 public interface IFileFacade 12 public interface IFileFacade
14 { 13 {
15 /// <summary> 14 /// <summary>
16 /// Reference to assembly application for this file.
17 /// </summary>
18 string AssemblyApplicationFileRef { get; }
19
20 /// <summary>
21 /// Reference to assembly manifest for this file.
22 /// </summary>
23 string AssemblyManifestFileRef { get; }
24
25 /// <summary>
26 /// List of assembly name values in the file.
27 /// </summary>
28 List<MsiAssemblyNameSymbol> AssemblyNames { get; set; }
29
30 /// <summary>
31 /// Optionally indicates what sort of assembly the file is.
32 /// </summary>
33 AssemblyType? AssemblyType { get; }
34
35 /// <summary>
36 /// Component containing the file. 15 /// Component containing the file.
37 /// </summary> 16 /// </summary>
38 string ComponentRef { get; } 17 string ComponentRef { get; }
@@ -58,21 +37,6 @@ namespace WixToolset.Extensibility.Data
58 int FileSize { get; set; } 37 int FileSize { get; set; }
59 38
60 /// <summary> 39 /// <summary>
61 /// Indicates whether the file came from a merge module.
62 /// </summary>
63 bool FromModule { get; }
64
65 /// <summary>
66 /// Indicates whether the file came from a transform.
67 /// </summary>
68 bool FromTransform { get; }
69
70 /// <summary>
71 /// Hash symbol of the file.
72 /// </summary>
73 MsiFileHashSymbol Hash { get; set; }
74
75 /// <summary>
76 /// Underlying identifier of the file. 40 /// Underlying identifier of the file.
77 /// </summary> 41 /// </summary>
78 Identifier Identifier { get; } 42 Identifier Identifier { get; }
@@ -118,9 +82,13 @@ namespace WixToolset.Extensibility.Data
118 string Version { get; set; } 82 string Version { get; set; }
119 83
120 /// <summary> 84 /// <summary>
121 /// Gets the underlying <c>FileRow</c> if one is present. 85 /// Calculated hash of the file.
86 /// </summary>
87 MsiFileHashSymbol MsiFileHashSymbol { get; set; }
88
89 /// <summary>
90 /// Assembly names found in the file.
122 /// </summary> 91 /// </summary>
123 /// <returns><c>FileRow</c> if one is present, otherwise throws.</returns> 92 ICollection<MsiAssemblyNameSymbol> AssemblyNameSymbols { get; }
124 FileRow GetFileRow();
125 } 93 }
126} 94}
diff --git a/src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileContext.cs b/src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileContext.cs
index 27d30a5a..7b974942 100644
--- a/src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileContext.cs
+++ b/src/api/wix/WixToolset.Extensibility/Data/IWindowsInstallerDecompileContext.cs
@@ -63,11 +63,6 @@ namespace WixToolset.Extensibility.Data
63 string IntermediateFolder { get; set; } 63 string IntermediateFolder { get; set; }
64 64
65 /// <summary> 65 /// <summary>
66 /// Gets or sets whether the decompiler admin image.
67 /// </summary>
68 bool IsAdminImage { get; set; }
69
70 /// <summary>
71 /// Gets or sets where to output the result. 66 /// Gets or sets where to output the result.
72 /// </summary> 67 /// </summary>
73 string OutputPath { get; set; } 68 string OutputPath { get; set; }
diff --git a/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs b/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs
index eff42b99..8bb1b2d6 100644
--- a/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs
+++ b/src/api/wix/WixToolset.Extensibility/Services/IBackendHelper.cs
@@ -5,8 +5,6 @@ namespace WixToolset.Extensibility.Services
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.Symbols;
9 using WixToolset.Data.WindowsInstaller.Rows;
10 using WixToolset.Extensibility.Data; 8 using WixToolset.Extensibility.Data;
11 9
12 /// <summary> 10 /// <summary>
@@ -15,28 +13,6 @@ namespace WixToolset.Extensibility.Services
15 public interface IBackendHelper : ILayoutServices 13 public interface IBackendHelper : ILayoutServices
16 { 14 {
17 /// <summary> 15 /// <summary>
18 /// Creates a file facade from a <c>FileSymbol</c> and possible <c>AssemblySymbol</c>.
19 /// </summary>
20 /// <param name="file"><c>FileSymbol</c> backing the facade.</param>
21 /// <param name="assembly"><c>AssemblySymbol</c> backing the facade.</param>
22 /// <returns></returns>
23 IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly);
24
25 /// <summary>
26 /// Creates a file facade from a File row.
27 /// </summary>
28 /// <param name="fileRow"><c>FileRow</c> </param>
29 /// <returns>New <c>IFileFacade</c>.</returns>
30 IFileFacade CreateFileFacade(FileRow fileRow);
31
32 /// <summary>
33 /// Creates a file facade from a Merge Module's File symbol.
34 /// </summary>
35 /// <param name="fileSymbol"><c>FileSymbol</c> created from a Merge Module.</param>
36 /// <returns>New <c>IFileFacade</c>.</returns>
37 IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol);
38
39 /// <summary>
40 /// Creates a MSI compatible GUID. 16 /// Creates a MSI compatible GUID.
41 /// </summary> 17 /// </summary>
42 /// <returns>Creates an uppercase GUID with braces.</returns> 18 /// <returns>Creates an uppercase GUID with braces.</returns>
diff --git a/src/api/wix/WixToolset.Extensibility/Services/IFileResolver.cs b/src/api/wix/WixToolset.Extensibility/Services/IFileResolver.cs
new file mode 100644
index 00000000..2804cc28
--- /dev/null
+++ b/src/api/wix/WixToolset.Extensibility/Services/IFileResolver.cs
@@ -0,0 +1,38 @@
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.Extensibility.Services
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Extensibility.Data;
8
9 /// <summary>
10 /// Interface to resolve file paths using extensions and bind paths.
11 /// </summary>
12 public interface IFileResolver
13 {
14 /// <summary>
15 /// Resolves the source path of a file using binder extensions.
16 /// </summary>
17 /// <param name="source">Original source value.</param>
18 /// <param name="librarianExtensions">Extensions used to resolve the file path.</param>
19 /// <param name="bindPaths">Collection of bind paths for the binding stage.</param>
20 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param>
21 /// <param name="symbolDefinition">Optional type of source file being resolved.</param>
22 /// <returns>Should return a valid path for the stream to be imported.</returns>
23 string ResolveFile(string source, IEnumerable<ILibrarianExtension> librarianExtensions, IEnumerable<IBindPath> bindPaths, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition);
24
25 /// <summary>
26 /// Resolves the source path of a file using binder extensions.
27 /// </summary>
28 /// <param name="source">Original source value.</param>
29 /// <param name="resolverExtensions">Extensions used to resolve the file path.</param>
30 /// <param name="bindPaths">Collection of bind paths for the binding stage.</param>
31 /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param>
32 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param>
33 /// <param name="symbolDefinition">Optional type of source file being resolved.</param>
34 /// <param name="alreadyCheckedPaths">Optional collection of paths already checked.</param>
35 /// <returns>Should return a valid path for the stream to be imported.</returns>
36 string ResolveFile(string source, IEnumerable<IResolverExtension> resolverExtensions, IEnumerable<IBindPath> bindPaths, BindStage bindStage, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, IEnumerable<string> alreadyCheckedPaths = null);
37 }
38}
diff --git a/src/api/wix/WixToolset.Extensibility/Services/IWindowsInstallerBackendHelper.cs b/src/api/wix/WixToolset.Extensibility/Services/IWindowsInstallerBackendHelper.cs
index 81325131..2216e957 100644
--- a/src/api/wix/WixToolset.Extensibility/Services/IWindowsInstallerBackendHelper.cs
+++ b/src/api/wix/WixToolset.Extensibility/Services/IWindowsInstallerBackendHelper.cs
@@ -3,7 +3,10 @@
3namespace WixToolset.Extensibility.Services 3namespace WixToolset.Extensibility.Services
4{ 4{
5 using WixToolset.Data; 5 using WixToolset.Data;
6 using WixToolset.Data.Symbols;
6 using WixToolset.Data.WindowsInstaller; 7 using WixToolset.Data.WindowsInstaller;
8 using WixToolset.Data.WindowsInstaller.Rows;
9 using WixToolset.Extensibility.Data;
7 10
8 /// <summary> 11 /// <summary>
9 /// Interface provided to help Windows Installer backend extensions. 12 /// Interface provided to help Windows Installer backend extensions.
@@ -11,6 +14,20 @@ namespace WixToolset.Extensibility.Services
11 public interface IWindowsInstallerBackendHelper : IBackendHelper 14 public interface IWindowsInstallerBackendHelper : IBackendHelper
12 { 15 {
13 /// <summary> 16 /// <summary>
17 /// Creates a file facade from a <c>FileSymbol</c>.
18 /// </summary>
19 /// <param name="file"><c>FileSymbol</c> backing the facade.</param>
20 /// <returns></returns>
21 IFileFacade CreateFileFacade(FileSymbol file);
22
23 /// <summary>
24 /// Creates a file facade from a File row.
25 /// </summary>
26 /// <param name="fileRow"><c>FileRow</c></param>
27 /// <returns>New <c>IFileFacade</c>.</returns>
28 IFileFacade CreateFileFacade(FileRow fileRow);
29
30 /// <summary>
14 /// Creates a <see cref="Row"/> in the specified table. 31 /// Creates a <see cref="Row"/> in the specified table.
15 /// </summary> 32 /// </summary>
16 /// <param name="section">Parent section.</param> 33 /// <param name="section">Parent section.</param>
diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs
index 93e7fc20..f2b3587d 100644
--- a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs
+++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs
@@ -35,21 +35,6 @@ namespace WixToolset.Core.Burn.ExtensibilityServices
35 35
36 #region IBackendHelper interfaces 36 #region IBackendHelper interfaces
37 37
38 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly)
39 {
40 return this.backendHelper.CreateFileFacade(file, assembly);
41 }
42
43 public IFileFacade CreateFileFacade(FileRow fileRow)
44 {
45 return this.backendHelper.CreateFileFacade(fileRow);
46 }
47
48 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol)
49 {
50 return this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol);
51 }
52
53 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) 38 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null)
54 { 39 {
55 return this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); 40 return this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers);
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs
index c4fddb3e..44b66cea 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs
@@ -35,4 +35,4 @@ namespace WixToolset.Core.WindowsInstaller.Bind
35 } 35 }
36 } 36 }
37 } 37 }
38} \ No newline at end of file 38}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs
index 66078d8d..6d37fdc2 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs
@@ -145,8 +145,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
145 var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId]; 145 var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId];
146 146
147 // Ensure that files are sequenced after the last file in any transform. 147 // Ensure that files are sequenced after the last file in any transform.
148 var transformMediaTable = mainTransform.Transform.Tables["Media"]; 148 if (mainTransform.Transform.Tables.TryGetTable("Media", out var transformMediaTable))
149 if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count)
150 { 149 {
151 foreach (MediaRow transformMediaRow in transformMediaTable.Rows) 150 foreach (MediaRow transformMediaRow in transformMediaTable.Rows)
152 { 151 {
@@ -370,16 +369,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
370 { 369 {
371 if (transform.TryGetTable(tableName, out var table)) 370 if (transform.TryGetTable(tableName, out var table))
372 { 371 {
373 foreach (var row in table.Rows) 372 foreach (var row in table.Rows.Where(r => r.Operation == RowOperation.Add))
374 { 373 {
375 if (row.Operation == RowOperation.Add) 374 success = false;
376 {
377 success = false;
378 375
379 var primaryKey = row.GetPrimaryKey('/') ?? String.Empty; 376 var primaryKey = row.GetPrimaryKey('/') ?? String.Empty;
380 377
381 this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey)); 378 this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey));
382 }
383 } 379 }
384 } 380 }
385 } 381 }
@@ -925,7 +921,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
925 for (var i = 0; i < propertyTable.Rows.Count; ++i) 921 for (var i = 0; i < propertyTable.Rows.Count; ++i)
926 { 922 {
927 var propertyRow = propertyTable.Rows[i]; 923 var propertyRow = propertyTable.Rows[i];
928 var property = (string)propertyRow[0]; 924 var property = propertyRow.FieldAsString(0);
929 925
930 if ("ProductCode" == property) 926 if ("ProductCode" == property)
931 { 927 {
@@ -1173,10 +1169,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1173 { 1169 {
1174 var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); 1170 var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition);
1175 1171
1176 foreach (FileRow mainFileRow in mainFileTable.Rows) 1172 foreach (var mainFileRow in mainFileTable.Rows.Cast<FileRow>())
1177 { 1173 {
1178 // Set File.Sequence to non null to satisfy transform bind. 1174 // Set File.Sequence to non null to satisfy transform bind and suppress any
1175 // change to File.Sequence to avoid bloat.
1179 mainFileRow.Sequence = 1; 1176 mainFileRow.Sequence = 1;
1177 mainFileRow.Fields[7].Modified = false;
1178
1179 // Override authored media to the media provided in the patch.
1180 mainFileRow.DiskId = mediaSymbol.DiskId;
1180 1181
1181 // Delete's don't need rows in the paired transform. 1182 // Delete's don't need rows in the paired transform.
1182 if (mainFileRow.Operation == RowOperation.Delete) 1183 if (mainFileRow.Operation == RowOperation.Delete)
@@ -1188,13 +1189,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1188 pairedFileRow.Operation = RowOperation.Modify; 1189 pairedFileRow.Operation = RowOperation.Modify;
1189 mainFileRow.CopyTo(pairedFileRow); 1190 mainFileRow.CopyTo(pairedFileRow);
1190 1191
1191 // Override authored media for patch bind. 1192 // Force modified File rows to appear in the transform.
1192 mainFileRow.DiskId = mediaSymbol.DiskId;
1193
1194 // Suppress any change to File.Sequence to avoid bloat.
1195 mainFileRow.Fields[7].Modified = false;
1196
1197 // Force File row to appear in the transform.
1198 switch (mainFileRow.Operation) 1193 switch (mainFileRow.Operation)
1199 { 1194 {
1200 case RowOperation.Modify: 1195 case RowOperation.Modify:
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index b849aea5..41559f8b 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -21,7 +21,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
21 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. 21 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
22 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); 22 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
23 23
24 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, IEnumerable<SubStorage> subStorages = null) 24 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, IEnumerable<SubStorage> patchSubStorages = null)
25 { 25 {
26 this.ServiceProvider = context.ServiceProvider; 26 this.ServiceProvider = context.ServiceProvider;
27 27
@@ -47,7 +47,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
47 this.ResolvedLcid = context.ResolvedLcid; 47 this.ResolvedLcid = context.ResolvedLcid;
48 this.SuppressLayout = context.SuppressLayout; 48 this.SuppressLayout = context.SuppressLayout;
49 49
50 this.SubStorages = subStorages; 50 this.PatchSubStorages = patchSubStorages;
51 51
52 this.BackendExtensions = backendExtension; 52 this.BackendExtensions = backendExtension;
53 } 53 }
@@ -76,7 +76,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
76 76
77 private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; } 77 private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; }
78 78
79 private IEnumerable<SubStorage> SubStorages { get; } 79 private IEnumerable<SubStorage> PatchSubStorages { get; }
80 80
81 private Intermediate Intermediate { get; } 81 private Intermediate Intermediate { get; }
82 82
@@ -180,35 +180,42 @@ namespace WixToolset.Core.WindowsInstaller.Bind
180 command.Execute(); 180 command.Execute();
181 } 181 }
182 182
183#if TODO_PATCHING 183 // Add missing CreateFolder symbols to null-keypath components.
184 ////if (OutputType.Patch == this.Output.Type) 184 {
185 ////{ 185 var command = new AddCreateFoldersCommand(section);
186 //// foreach (SubStorage substorage in this.Output.SubStorages) 186 command.Execute();
187 //// { 187 }
188 //// Output transform = substorage.Data;
189
190 //// ResolveFieldsCommand command = new ResolveFieldsCommand();
191 //// command.Tables = transform.Tables;
192 //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
193 //// command.FileManagerCore = this.FileManagerCore;
194 //// command.FileManagers = this.FileManagers;
195 //// command.SupportDelayedResolution = false;
196 //// command.TempFilesLocation = this.TempFilesLocation;
197 //// command.WixVariableResolver = this.WixVariableResolver;
198 //// command.Execute();
199
200 //// this.MergeUnrealTables(transform.Tables);
201 //// }
202 ////}
203#endif
204 188
205 if (this.Messaging.EncounteredError) 189 if (this.Messaging.EncounteredError)
206 { 190 {
207 return null; 191 return null;
208 } 192 }
209 193
210 this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); 194 // Process dependency references.
211 this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); 195 if (SectionType.Product == section.Type || SectionType.Module == section.Type)
196 {
197 var dependencyRefs = section.Symbols.OfType<WixDependencyRefSymbol>().ToList();
198
199 if (dependencyRefs.Any())
200 {
201 var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs);
202 command.Execute();
203 }
204 }
205
206 // Process SoftwareTags in MSI packages.
207 if (SectionType.Product == section.Type)
208 {
209 var softwareTags = section.Symbols.OfType<WixPackageTagSymbol>().ToList();
210
211 if (softwareTags.Any())
212 {
213 var command = new ProcessPackageSoftwareTagsCommand(section, this.WindowsInstallerBackendHelper, softwareTags, this.IntermediateFolder);
214 command.Execute();
215
216 trackedFiles.AddRange(command.TrackedFiles);
217 }
218 }
212 219
213 // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). 220 // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules).
214 { 221 {
@@ -217,21 +224,33 @@ namespace WixToolset.Core.WindowsInstaller.Bind
217 trackedFiles.AddRange(extractedFiles); 224 trackedFiles.AddRange(extractedFiles);
218 } 225 }
219 226
227 // Update symbols that reference text files on disk. Some of those files may have come from .wixlibs and WixExtensions
228 // extracted above.
229 {
230 var command = new UpdateFromTextFilesCommand(this.Messaging, section);
231 command.Execute();
232 }
233
234 this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound);
235 this.Messaging.Write(VerboseMessages.UpdatingFileInformation());
236
220 // This must occur after all variables and source paths have been resolved. 237 // This must occur after all variables and source paths have been resolved.
221 List<IFileFacade> fileFacades; 238 List<IFileFacade> allFileFacades;
222 if (SectionType.Patch == section.Type) 239 List<IFileFacade> fileFacadesFromIntermediate;
240 List<IFileFacade> fileFacadesFromModule = null;
241 if (section.Type == SectionType.Patch)
223 { 242 {
224 var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.SubStorages); 243 var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.PatchSubStorages);
225 command.Execute(); 244 command.Execute();
226 245
227 fileFacades = command.FileFacades; 246 allFileFacades = fileFacadesFromIntermediate = command.FileFacades;
228 } 247 }
229 else 248 else
230 { 249 {
231 var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper); 250 var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper);
232 command.Execute(); 251 command.Execute();
233 252
234 fileFacades = command.FileFacades; 253 allFileFacades = fileFacadesFromIntermediate = command.FileFacades;
235 } 254 }
236 255
237 // Retrieve file information from merge modules. 256 // Retrieve file information from merge modules.
@@ -243,10 +262,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
243 { 262 {
244 containsMergeModules = true; 263 containsMergeModules = true;
245 264
246 var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacades, installerVersion, this.IntermediateFolder, this.SuppressLayout); 265 var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacadesFromIntermediate, installerVersion, this.IntermediateFolder, this.SuppressLayout);
247 command.Execute(); 266 command.Execute();
248 267
249 fileFacades.AddRange(command.MergeModulesFileFacades); 268 fileFacadesFromModule = new List<IFileFacade>(command.MergeModulesFileFacades);
269 allFileFacades.AddRange(fileFacadesFromModule);
250 trackedFiles.AddRange(command.TrackedFiles); 270 trackedFiles.AddRange(command.TrackedFiles);
251 } 271 }
252 } 272 }
@@ -257,23 +277,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
257 return null; 277 return null;
258 } 278 }
259 279
260 // Process SoftwareTags in MSI packages.
261 if (SectionType.Product == section.Type)
262 {
263 var softwareTags = section.Symbols.OfType<WixPackageTagSymbol>().ToList();
264
265 if (softwareTags.Any())
266 {
267 var command = new ProcessPackageSoftwareTagsCommand(section, this.WindowsInstallerBackendHelper, softwareTags, this.IntermediateFolder);
268 command.Execute();
269
270 trackedFiles.AddRange(command.TrackedFiles);
271 }
272 }
273
274 // Gather information about files that do not come from merge modules. 280 // Gather information about files that do not come from merge modules.
275 { 281 {
276 var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); 282 var command = new UpdateFileFacadesCommand(this.Messaging, section, allFileFacades, fileFacadesFromIntermediate, variableCache, overwriteHash: true);
277 command.Execute(); 283 command.Execute();
278 } 284 }
279 285
@@ -289,18 +295,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
289 this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); 295 this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache);
290 } 296 }
291 297
292 // Update symbols that reference text files on disk.
293 {
294 var command = new UpdateFromTextFilesCommand(this.Messaging, section);
295 command.Execute();
296 }
297
298 // Add missing CreateFolder symbols to null-keypath components.
299 {
300 var command = new AddCreateFoldersCommand(section);
301 command.Execute();
302 }
303
304 // Now that delayed fields are processed, fixup the package version (if needed) and validate it 298 // Now that delayed fields are processed, fixup the package version (if needed) and validate it
305 // which will short circuit duplicate errors later if the ProductVersion is invalid. 299 // which will short circuit duplicate errors later if the ProductVersion is invalid.
306 if (SectionType.Product == section.Type) 300 if (SectionType.Product == section.Type)
@@ -308,18 +302,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
308 this.ProcessProductVersion(packageSymbol, section, validate: true); 302 this.ProcessProductVersion(packageSymbol, section, validate: true);
309 } 303 }
310 304
311 // Process dependency references.
312 if (SectionType.Product == section.Type || SectionType.Module == section.Type)
313 {
314 var dependencyRefs = section.Symbols.OfType<WixDependencyRefSymbol>().ToList();
315
316 if (dependencyRefs.Any())
317 {
318 var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs);
319 command.Execute();
320 }
321 }
322
323 // If there are any backend extensions, give them the opportunity to process 305 // If there are any backend extensions, give them the opportunity to process
324 // the section now that the fields have all be resolved. 306 // the section now that the fields have all be resolved.
325 // 307 //
@@ -339,9 +321,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
339 321
340 if (reresolvedFiles.Any()) 322 if (reresolvedFiles.Any())
341 { 323 {
342 var updatedFacades = reresolvedFiles.Select(f => fileFacades.First(ff => ff.Id == f.Id?.Id)); 324 var updatedFacades = reresolvedFiles.Select(f => allFileFacades.First(ff => ff.Id == f.Id?.Id));
343 325
344 var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updatedFacades, variableCache, overwriteHash: false); 326 var command = new UpdateFileFacadesCommand(this.Messaging, section, allFileFacades, updatedFacades, variableCache, overwriteHash: false);
345 command.Execute(); 327 command.Execute();
346 } 328 }
347 } 329 }
@@ -363,28 +345,22 @@ namespace WixToolset.Core.WindowsInstaller.Bind
363 } 345 }
364 } 346 }
365 347
366 // Set generated component guids and validate all guids.
367 {
368 var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform);
369 command.Execute();
370 }
371
372 // Assign files to media and update file sequences. 348 // Assign files to media and update file sequences.
373 Dictionary<MediaSymbol, IEnumerable<IFileFacade>> filesByCabinetMedia; 349 Dictionary<MediaSymbol, IEnumerable<IFileFacade>> filesByCabinetMedia;
374 IEnumerable<IFileFacade> uncompressedFiles; 350 IEnumerable<IFileFacade> uncompressedFiles;
375 { 351 {
376 var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, fileFacades); 352 var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, allFileFacades);
377 order.Execute(); 353 order.Execute();
378 354
379 fileFacades = order.FileFacades; 355 allFileFacades = order.FileFacades;
380 356
381 var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); 357 var assign = new AssignMediaCommand(section, this.Messaging, allFileFacades, compressed);
382 assign.Execute(); 358 assign.Execute();
383 359
384 filesByCabinetMedia = assign.FileFacadesByCabinetMedia; 360 filesByCabinetMedia = assign.FileFacadesByCabinetMedia;
385 uncompressedFiles = assign.UncompressedFileFacades; 361 uncompressedFiles = assign.UncompressedFileFacades;
386 362
387 var update = new UpdateMediaSequencesCommand(section, fileFacades); 363 var update = new UpdateMediaSequencesCommand(section, allFileFacades);
388 update.Execute(); 364 update.Execute();
389 } 365 }
390 366
@@ -394,6 +370,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind
394 return null; 370 return null;
395 } 371 }
396 372
373 // Copy updated file facade data back into the transforms or symbols as appropriate.
374 if (section.Type == SectionType.Patch)
375 {
376 var command = new UpdateTransformsWithFileFacades(this.Messaging, section, this.PatchSubStorages, tableDefinitions, allFileFacades);
377 command.Execute();
378 }
379 else
380 {
381 var command = new UpdateSymbolsWithFileFacadesCommand(section, allFileFacades);
382 command.Execute();
383 }
384
385 // Set generated component guids and validate all guids.
386 {
387 var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform);
388 command.Execute();
389 }
390
397 // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better. 391 // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better.
398 WindowsInstallerData data; 392 WindowsInstallerData data;
399 { 393 {
@@ -412,9 +406,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind
412 var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions); 406 var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions);
413 suppressedTableNames = unsuppress.Execute(); 407 suppressedTableNames = unsuppress.Execute();
414 } 408 }
409 else if (data.Type == OutputType.Product) // we can create instance transforms since Component Guids and Outputs are created.
410 {
411 var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper);
412 command.Execute();
413
414 foreach (var storage in command.SubStorages)
415 {
416 data.SubStorages.Add(storage);
417 }
418 }
415 else if (data.Type == OutputType.Patch) 419 else if (data.Type == OutputType.Patch)
416 { 420 {
417 foreach (var storage in this.SubStorages) 421 foreach (var storage in this.PatchSubStorages)
418 { 422 {
419 data.SubStorages.Add(storage); 423 data.SubStorages.Add(storage);
420 } 424 }
@@ -426,13 +430,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
426 return null; 430 return null;
427 } 431 }
428 432
429 // Ensure the intermediate folder is created since delta patches will be 433 if (section.Type == SectionType.Patch && this.DeltaBinaryPatch)
430 // created there.
431 Directory.CreateDirectory(this.IntermediateFolder);
432
433 if (SectionType.Patch == section.Type && this.DeltaBinaryPatch)
434 { 434 {
435 var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType<WixPatchSymbol>().FirstOrDefault()); 435 var command = new CreateDeltaPatchesCommand(allFileFacades, this.IntermediateFolder, section.Symbols.OfType<WixPatchSymbol>().FirstOrDefault());
436 command.Execute(); 436 command.Execute();
437 } 437 }
438 438
@@ -454,19 +454,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
454 return null; 454 return null;
455 } 455 }
456 456
457 // We can create instance transforms since Component Guids and Outputs are created.
458 if (data.Type == OutputType.Product)
459 {
460 var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper);
461 command.Execute();
462 }
463 else if (data.Type == OutputType.Patch)
464 {
465 // Copy output data back into the transforms.
466 var command = new UpdateTransformsWithFileFacades(this.Messaging, data, this.SubStorages, tableDefinitions, fileFacades);
467 command.Execute();
468 }
469
470 // Generate database file. 457 // Generate database file.
471 { 458 {
472 this.Messaging.Write(VerboseMessages.GeneratingDatabase()); 459 this.Messaging.Write(VerboseMessages.GeneratingDatabase());
@@ -491,7 +478,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
491 { 478 {
492 this.Messaging.Write(VerboseMessages.MergingModules()); 479 this.Messaging.Write(VerboseMessages.MergingModules());
493 480
494 var command = new MergeModulesCommand(this.Messaging, this.WindowsInstallerBackendHelper, fileFacades, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); 481 var command = new MergeModulesCommand(this.Messaging, this.WindowsInstallerBackendHelper, fileFacadesFromModule, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder);
495 command.Execute(); 482 command.Execute();
496 483
497 trackedFiles.AddRange(command.TrackedFiles); 484 trackedFiles.AddRange(command.TrackedFiles);
@@ -518,7 +505,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
518 var fi = new FileInfo(this.OutputPath); 505 var fi = new FileInfo(this.OutputPath);
519 if (fi.Length > Int32.MaxValue) 506 if (fi.Length > Int32.MaxValue)
520 { 507 {
521 this.Messaging.Write(WarningMessages.WindowsInstallerFileTooLarge(null, this.OutputPath, "MSI")); 508 this.Messaging.Write(WarningMessages.WindowsInstallerFileTooLarge(null, this.OutputPath, data.Type.ToString()));
522 } 509 }
523 } 510 }
524 catch 511 catch
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
index 9acbe475..93aad0a6 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
@@ -169,18 +169,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind
169 } 169 }
170 } 170 }
171 171
172 // create the cabinet file 172 // Calculate the files to be compressed into the cabinet.
173 var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile); 173 var compressFiles = new List<CabinetCompressFile>();
174
175 foreach (var facade in cabinetWorkItem.FileFacades.OrderBy(f => f.Sequence))
176 {
177 var modularizedId = facade.Id + cabinetWorkItem.ModularizationSuffix;
174 178
175 var files = cabinetWorkItem.FileFacades 179 var compressFile = cabinetWorkItem.HashesByFileId.TryGetValue(facade.Id, out var hash) ?
176 .OrderBy(f => f.Sequence) 180 new CabinetCompressFile(facade.SourcePath, modularizedId, hash.HashPart1, hash.HashPart2, hash.HashPart3, hash.HashPart4) :
177 .Select(facade => facade.Hash == null ? 181 new CabinetCompressFile(facade.SourcePath, modularizedId);
178 new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix) :
179 new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4))
180 .ToList();
181 182
183 compressFiles.Add(compressFile);
184 }
185
186 // create the cabinet file
187 var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile);
182 var cab = new Cabinet(cabinetPath); 188 var cab = new Cabinet(cabinetPath);
183 var created = cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); 189 var created = cab.Compress(compressFiles, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold);
184 190
185 // Best effort check to see if the cabinet is too large for the Windows Installer. 191 // Best effort check to see if the cabinet is too large for the Windows Installer.
186 try 192 try
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs
index 12332c80..d9bc7a7e 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs
@@ -4,6 +4,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System.Collections.Generic; 5 using System.Collections.Generic;
6 using WixToolset.Data; 6 using WixToolset.Data;
7 using WixToolset.Data.Symbols;
7 using WixToolset.Extensibility.Data; 8 using WixToolset.Extensibility.Data;
8 9
9 /// <summary> 10 /// <summary>
@@ -17,11 +18,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
17 /// <param name="sourceLineNumber">Source line number that requires the cabinet creation.</param> 18 /// <param name="sourceLineNumber">Source line number that requires the cabinet creation.</param>
18 /// <param name="diskId"></param> 19 /// <param name="diskId"></param>
19 /// <param name="fileFacades">The collection of files in this cabinet.</param> 20 /// <param name="fileFacades">The collection of files in this cabinet.</param>
21 /// <param name="hashesByFileId">The hashes for unversioned files.</param>
20 /// <param name="cabinetFile">The cabinet file.</param> 22 /// <param name="cabinetFile">The cabinet file.</param>
21 /// <param name="maxThreshold">Maximum threshold for each cabinet.</param> 23 /// <param name="maxThreshold">Maximum threshold for each cabinet.</param>
22 /// <param name="compressionLevel">The compression level of the cabinet.</param> 24 /// <param name="compressionLevel">The compression level of the cabinet.</param>
23 /// <param name="modularizationSuffix">Modularization suffix used when building a Merge Module.</param> 25 /// <param name="modularizationSuffix">Modularization suffix used when building a Merge Module.</param>
24 public CabinetWorkItem(SourceLineNumber sourceLineNumber, int diskId, string cabinetFile, IEnumerable<IFileFacade> fileFacades, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix) 26 public CabinetWorkItem(SourceLineNumber sourceLineNumber, int diskId, string cabinetFile, IEnumerable<IFileFacade> fileFacades, Dictionary<string, MsiFileHashSymbol> hashesByFileId, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix)
25 { 27 {
26 this.SourceLineNumber = sourceLineNumber; 28 this.SourceLineNumber = sourceLineNumber;
27 this.DiskId = diskId; 29 this.DiskId = diskId;
@@ -29,6 +31,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
29 this.CompressionLevel = compressionLevel; 31 this.CompressionLevel = compressionLevel;
30 this.ModularizationSuffix = modularizationSuffix; 32 this.ModularizationSuffix = modularizationSuffix;
31 this.FileFacades = fileFacades; 33 this.FileFacades = fileFacades;
34 this.HashesByFileId = hashesByFileId;
32 this.MaxThreshold = maxThreshold; 35 this.MaxThreshold = maxThreshold;
33 } 36 }
34 37
@@ -66,6 +69,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
66 public IEnumerable<IFileFacade> FileFacades { get; } 69 public IEnumerable<IFileFacade> FileFacades { get; }
67 70
68 /// <summary> 71 /// <summary>
72 /// The hashes for unversioned files.
73 /// </summary>
74 public Dictionary<string, MsiFileHashSymbol> HashesByFileId { get; }
75
76 /// <summary>
69 /// Gets the max threshold. 77 /// Gets the max threshold.
70 /// </summary> 78 /// </summary>
71 /// <value>The maximum threshold for a folder in a cabinet.</value> 79 /// <value>The maximum threshold for a folder in a cabinet.</value>
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
index 6ed107d5..1ed2ba79 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
@@ -95,6 +95,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
95 95
96 var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, maximumCabinetSizeForLargeFileSplitting, maximumUncompressedMediaSize); 96 var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, maximumCabinetSizeForLargeFileSplitting, maximumUncompressedMediaSize);
97 97
98 var hashesByFileId = this.Section.Symbols.OfType<MsiFileHashSymbol>().ToDictionary(s => s.Id.Id);
99
98 foreach (var entry in this.FileFacadesByCabinet) 100 foreach (var entry in this.FileFacadesByCabinet)
99 { 101 {
100 var mediaSymbol = entry.Key; 102 var mediaSymbol = entry.Key;
@@ -102,7 +104,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
102 var compressionLevel = mediaSymbol.CompressionLevel ?? this.DefaultCompressionLevel ?? CompressionLevel.Medium; 104 var compressionLevel = mediaSymbol.CompressionLevel ?? this.DefaultCompressionLevel ?? CompressionLevel.Medium;
103 var cabinetDir = this.ResolveMedia(mediaSymbol, mediaSymbol.Layout, this.LayoutDirectory); 105 var cabinetDir = this.ResolveMedia(mediaSymbol, mediaSymbol.Layout, this.LayoutDirectory);
104 106
105 var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files); 107 var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files, hashesByFileId);
106 if (null != cabinetWorkItem) 108 if (null != cabinetWorkItem)
107 { 109 {
108 cabinetBuilder.Enqueue(cabinetWorkItem); 110 cabinetBuilder.Enqueue(cabinetWorkItem);
@@ -140,16 +142,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
140 return cabbingThreadCount; 142 return cabbingThreadCount;
141 } 143 }
142 144
143 /// <summary> 145 private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable<IFileFacade> fileFacades, Dictionary<string, MsiFileHashSymbol> hashesByFileId)
144 /// Creates a work item to create a cabinet.
145 /// </summary>
146 /// <param name="data">Windows Installer data for the current database.</param>
147 /// <param name="cabinetDir">Directory to create cabinet in.</param>
148 /// <param name="mediaSymbol">Media symbol containing information about the cabinet.</param>
149 /// <param name="compressionLevel">Desired compression level.</param>
150 /// <param name="fileFacades">Collection of files in this cabinet.</param>
151 /// <returns>created CabinetWorkItem object</returns>
152 private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable<IFileFacade> fileFacades)
153 { 146 {
154 CabinetWorkItem cabinetWorkItem = null; 147 CabinetWorkItem cabinetWorkItem = null;
155 148
@@ -171,7 +164,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
171 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) 164 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
172 { 165 {
173 // Default to the threshold for best smartcabbing (makes smallest cabinet). 166 // Default to the threshold for best smartcabbing (makes smallest cabinet).
174 cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, mediaSymbol.DiskId, resolvedCabinet.Path, fileFacades, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix); 167 cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, mediaSymbol.DiskId, resolvedCabinet.Path, fileFacades, hashesByFileId, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix);
175 } 168 }
176 else // reuse the cabinet from the cabinet cache. 169 else // reuse the cabinet from the cabinet cache.
177 { 170 {
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs
index d0e25571..1d480250 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs
@@ -30,8 +30,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
30 30
31 private IBackendHelper BackendHelper { get; } 31 private IBackendHelper BackendHelper { get; }
32 32
33 public void Execute() 33 public IReadOnlyCollection<SubStorage> SubStorages { get; private set; }
34
35 public IReadOnlyCollection<SubStorage> Execute()
34 { 36 {
37 var subStorages = new List<SubStorage>();
38
35 // Create and add substorages for instance transforms. 39 // Create and add substorages for instance transforms.
36 var wixInstanceTransformsSymbols = this.Section.Symbols.OfType<WixInstanceTransformsSymbol>(); 40 var wixInstanceTransformsSymbols = this.Section.Symbols.OfType<WixInstanceTransformsSymbol>();
37 41
@@ -252,9 +256,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
252 summaryRow[1] = "4"; 256 summaryRow[1] = "4";
253 } 257 }
254 258
255 this.Output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); 259 subStorages.Add(new SubStorage(instanceId, instanceTransform));
256 } 260 }
257 } 261 }
262
263 return this.SubStorages = subStorages;
258 } 264 }
259 } 265 }
260} 266}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
index 5c993f63..6d5bff69 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
@@ -6,31 +6,44 @@ 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.Msi;
10 using WixToolset.Core.WindowsInstaller.Unbind; 9 using WixToolset.Core.WindowsInstaller.Unbind;
11 using WixToolset.Data; 10 using WixToolset.Data;
12 using WixToolset.Data.Symbols; 11 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller; 12 using WixToolset.Data.WindowsInstaller;
13 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services; 15 using WixToolset.Extensibility.Services;
15 16
16 internal class CreatePatchTransformsCommand 17 internal class CreatePatchTransformsCommand
17 { 18 {
18 public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, string intermediateFolder) 19 public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, IFileResolver fileResolver, IReadOnlyCollection<IResolverExtension> resolverExtensions, Intermediate intermediate, string intermediateFolder, IReadOnlyCollection<IBindPath> bindPaths)
19 { 20 {
20 this.Messaging = messaging; 21 this.Messaging = messaging;
21 this.BackendHelper = backendHelper; 22 this.BackendHelper = backendHelper;
23 this.PathResolver = pathResolver;
24 this.FileResolver = fileResolver;
25 this.ResolverExtensions = resolverExtensions;
22 this.Intermediate = intermediate; 26 this.Intermediate = intermediate;
23 this.IntermediateFolder = intermediateFolder; 27 this.IntermediateFolder = intermediateFolder;
28 this.BindPaths = bindPaths;
24 } 29 }
25 30
26 private IMessaging Messaging { get; } 31 private IMessaging Messaging { get; }
27 32
28 private IBackendHelper BackendHelper { get; } 33 private IBackendHelper BackendHelper { get; }
29 34
35 private IPathResolver PathResolver { get; }
36
37 private IFileResolver FileResolver { get; }
38
39 private IReadOnlyCollection<IResolverExtension> ResolverExtensions { get; }
40
30 private Intermediate Intermediate { get; } 41 private Intermediate Intermediate { get; }
31 42
32 private string IntermediateFolder { get; } 43 private string IntermediateFolder { get; }
33 44
45 private IReadOnlyCollection<IBindPath> BindPaths { get; }
46
34 public IEnumerable<PatchTransform> PatchTransforms { get; private set; } 47 public IEnumerable<PatchTransform> PatchTransforms { get; private set; }
35 48
36 public IEnumerable<PatchTransform> Execute() 49 public IEnumerable<PatchTransform> Execute()
@@ -41,23 +54,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
41 54
42 foreach (var symbol in symbols) 55 foreach (var symbol in symbols)
43 { 56 {
44 WindowsInstallerData transform; 57 var targetData = this.GetWindowsInstallerData(symbol.BaselineFile.Path, BindStage.Target);
45 58 var updatedData = this.GetWindowsInstallerData(symbol.UpdateFile.Path, BindStage.Updated);
46 if (symbol.TransformFile is null)
47 {
48 var baselineData = this.GetData(symbol.BaselineFile.Path);
49 var updateData = this.GetData(symbol.UpdateFile.Path);
50 59
51 var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false); 60 var command = new GenerateTransformCommand(this.Messaging, targetData, updatedData, preserveUnchangedRows: true, showPedanticMessages: false);
52 transform = command.Execute(); 61 var transform = command.Execute();
53 }
54 else
55 {
56 var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path.
57
58 var command = new UnbindTransformCommand(this.Messaging, this.BackendHelper, symbol.TransformFile.Path, exportBasePath, this.IntermediateFolder);
59 transform = command.Execute();
60 }
61 62
62 patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform)); 63 patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform));
63 } 64 }
@@ -67,26 +68,61 @@ namespace WixToolset.Core.WindowsInstaller.Bind
67 return this.PatchTransforms; 68 return this.PatchTransforms;
68 } 69 }
69 70
70 private WindowsInstallerData GetData(string path) 71 private WindowsInstallerData GetWindowsInstallerData(string path, BindStage stage)
71 { 72 {
72 var ext = Path.GetExtension(path); 73 if (DataLoader.TryLoadWindowsInstallerData(path, true, out var data))
73
74 if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase))
75 { 74 {
76 using (var database = new Database(path, OpenDatabase.ReadOnly)) 75 // Re-resolve file paths only when loading from .wixpdb.
77 { 76 this.ReResolveWindowsInstallerData(data, stage);
78 var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path. 77 }
78 else
79 {
80 var stageFolder = $"_{stage.ToString().ToLowerInvariant()}_msi";
81 var exportBasePath = Path.Combine(this.IntermediateFolder, stageFolder);
82 var extractFilesFolder = Path.Combine(exportBasePath, "File");
83
84 var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, path, OutputType.Product, exportBasePath, extractFilesFolder, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: false);
85 data = command.Execute();
86 }
79 87
80 var isAdminImage = false; // TODO: need a better way to set this 88 return data;
89 }
81 90
82 var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true); 91 private void ReResolveWindowsInstallerData(WindowsInstallerData data, BindStage stage)
83 return command.Execute(); 92 {
84 } 93 var bindPaths = this.BindPaths.Where(b => b.Stage == stage).ToList();
94
95 if (bindPaths.Count == 0)
96 {
97 return;
85 } 98 }
86 else // assume .wixpdb (or .wixout) 99
100 foreach (var table in data.Tables)
87 { 101 {
88 var data = WindowsInstallerData.Load(path, true); 102 foreach (var row in table.Rows)
89 return data; 103 {
104 foreach (var field in row.Fields.Where(f => f.Column.Type == ColumnType.Object))
105 {
106 if (field.PreviousData != null)
107 {
108 try
109 {
110 var originalPath = field.AsString();
111
112 var resolvedPath = this.FileResolver.ResolveFile(field.PreviousData, this.ResolverExtensions, bindPaths, stage, row.SourceLineNumbers, null);
113
114 if (!String.Equals(originalPath, resolvedPath, StringComparison.OrdinalIgnoreCase))
115 {
116 field.Data = resolvedPath;
117 }
118 }
119 catch (WixException e)
120 {
121 this.Messaging.Write(e.Error);
122 }
123 }
124 }
125 }
90 } 126 }
91 } 127 }
92 } 128 }
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs
index 60317cd9..4d99ff40 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs
@@ -662,10 +662,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
662 row.FileSize = symbol.FileSize; 662 row.FileSize = symbol.FileSize;
663 row.Version = symbol.Version; 663 row.Version = symbol.Version;
664 row.Language = symbol.Language; 664 row.Language = symbol.Language;
665 row.DiskId = symbol.DiskId ?? 1; // TODO: is 1 the correct thing to default here
666 row.Sequence = symbol.Sequence; 665 row.Sequence = symbol.Sequence;
666 row.DiskId = symbol.DiskId ?? throw new InvalidDataException("FileSymbol.DiskId should have been initialized before creating WindowsInstallerData from IntermediateRepresentation.");
667 row.Source = symbol.Source.Path; 667 row.Source = symbol.Source.Path;
668 668
669 var previousSourceField = symbol.Fields[(int)FileSymbolFields.Source]?.PreviousValue;
670 row.PreviousSource = previousSourceField?.AsPath().Path;
671
669 var attributes = (symbol.Attributes & FileSymbolAttributes.Checksum) == FileSymbolAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0; 672 var attributes = (symbol.Attributes & FileSymbolAttributes.Checksum) == FileSymbolAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0;
670 attributes |= (symbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0; 673 attributes |= (symbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0;
671 attributes |= (symbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0; 674 attributes |= (symbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0;
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/DataLoader.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/DataLoader.cs
new file mode 100644
index 00000000..6214bbdb
--- /dev/null
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/DataLoader.cs
@@ -0,0 +1,49 @@
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 using System;
6 using System.IO;
7 using WixToolset.Data.WindowsInstaller;
8
9 internal class DataLoader
10 {
11 public static bool TryLoadWindowsInstallerData(string path, out WindowsInstallerData data)
12 {
13 return TryLoadWindowsInstallerData(path, false, out data);
14 }
15
16 public static bool TryLoadWindowsInstallerData(string path, bool suppressVersionCheck, out WindowsInstallerData data)
17 {
18 data = null;
19
20 var extension = Path.GetExtension(path);
21
22 // If the path is _not_ obviously a Windows Installer database, let's try opening it as
23 // our own data file format.
24 if (!extension.Equals(".msi", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".msm", StringComparison.OrdinalIgnoreCase))
25 {
26 (data, _) = LoadWindowsInstallerDataSafely(path, suppressVersionCheck);
27 }
28
29 return data != null;
30 }
31
32 public static (WindowsInstallerData, Exception) LoadWindowsInstallerDataSafely(string path, bool suppressVersionCheck = false)
33 {
34 WindowsInstallerData data = null;
35 Exception exception = null;
36
37 try
38 {
39 data = WindowsInstallerData.Load(path, suppressVersionCheck);
40 }
41 catch (Exception e)
42 {
43 exception = e;
44 }
45
46 return (data, exception);
47 }
48 }
49}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
index 06168727..94ed0afc 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
@@ -22,12 +22,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
22 /// </summary> 22 /// </summary>
23 internal class ExtractMergeModuleFilesCommand 23 internal class ExtractMergeModuleFilesCommand
24 { 24 {
25 public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable<WixMergeSymbol> wixMergeSymbols, IEnumerable<IFileFacade> fileFacades, int installerVersion, string intermediateFolder, bool suppressLayout) 25 public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable<WixMergeSymbol> wixMergeSymbols, IEnumerable<IFileFacade> fileFacadesFromIntermediate, int installerVersion, string intermediateFolder, bool suppressLayout)
26 { 26 {
27 this.Messaging = messaging; 27 this.Messaging = messaging;
28 this.BackendHelper = backendHelper; 28 this.BackendHelper = backendHelper;
29 this.WixMergeSymbols = wixMergeSymbols; 29 this.WixMergeSymbols = wixMergeSymbols;
30 this.FileFacades = fileFacades; 30 this.FileFacadesFromIntermediate = fileFacadesFromIntermediate;
31 this.OutputInstallerVersion = installerVersion; 31 this.OutputInstallerVersion = installerVersion;
32 this.IntermediateFolder = intermediateFolder; 32 this.IntermediateFolder = intermediateFolder;
33 this.SuppressLayout = suppressLayout; 33 this.SuppressLayout = suppressLayout;
@@ -39,7 +39,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
39 39
40 private IEnumerable<WixMergeSymbol> WixMergeSymbols { get; } 40 private IEnumerable<WixMergeSymbol> WixMergeSymbols { get; }
41 41
42 private IEnumerable<IFileFacade> FileFacades { get; } 42 private IEnumerable<IFileFacade> FileFacadesFromIntermediate { get; }
43 43
44 private int OutputInstallerVersion { get; } 44 private int OutputInstallerVersion { get; }
45 45
@@ -65,7 +65,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
65 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let 65 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let
66 // this case be slightly more expensive because the cost of maintaining an indexed file row collection 66 // this case be slightly more expensive because the cost of maintaining an indexed file row collection
67 // is a lot more costly for the common cases. 67 // is a lot more costly for the common cases.
68 var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal); 68 var indexedFileFacades = this.FileFacadesFromIntermediate.ToDictionary(f => f.Id, StringComparer.Ordinal);
69 69
70 foreach (var wixMergeRow in this.WixMergeSymbols) 70 foreach (var wixMergeRow in this.WixMergeSymbols)
71 { 71 {
@@ -115,7 +115,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
115 fileSymbol.DiskId = wixMergeRow.DiskId; 115 fileSymbol.DiskId = wixMergeRow.DiskId;
116 fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) }; 116 fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) };
117 117
118 var mergeModuleFileFacade = this.BackendHelper.CreateFileFacadeFromMergeModule(fileSymbol); 118 var mergeModuleFileFacade = this.BackendHelper.CreateFileFacade(fileSymbol);
119 119
120 // If case-sensitive collision with another merge module or a user-authored file identifier. 120 // If case-sensitive collision with another merge module or a user-authored file identifier.
121 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade)) 121 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade))
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs
index 575065bb..92a0e11f 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs
@@ -16,7 +16,7 @@ namespace WixToolset.Core.WindowsInstaller
16 /// </summary> 16 /// </summary>
17 internal class GenerateTransformCommand 17 internal class GenerateTransformCommand
18 { 18 {
19 private const char sectionDelimiter = '/'; 19 private const char SectionDelimiter = '/';
20 private readonly IMessaging messaging; 20 private readonly IMessaging messaging;
21 private SummaryInformationStreams transformSummaryInfo; 21 private SummaryInformationStreams transformSummaryInfo;
22 22
@@ -38,22 +38,10 @@ namespace WixToolset.Core.WindowsInstaller
38 38
39 private TransformFlags ValidationFlags { get; } 39 private TransformFlags ValidationFlags { get; }
40 40
41 /// <summary>
42 /// Gets or sets the option to show pedantic messages.
43 /// </summary>
44 /// <value>The option to show pedantic messages.</value>
45 private bool ShowPedanticMessages { get; } 41 private bool ShowPedanticMessages { get; }
46 42
47 /// <summary>
48 /// Gets or sets the option to suppress keeping special rows.
49 /// </summary>
50 /// <value>The option to suppress keeping special rows.</value>
51 private bool SuppressKeepingSpecialRows { get; } 43 private bool SuppressKeepingSpecialRows { get; }
52 44
53 /// <summary>
54 /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output.
55 /// </summary>
56 /// <value>The option to keep all rows including unchanged rows.</value>
57 private bool PreserveUnchangedRows { get; } 45 private bool PreserveUnchangedRows { get; }
58 46
59 public WindowsInstallerData Transform { get; private set; } 47 public WindowsInstallerData Transform { get; private set; }
@@ -124,7 +112,7 @@ namespace WixToolset.Core.WindowsInstaller
124 foreach (var updatedRow in updatedTable.Rows) 112 foreach (var updatedRow in updatedTable.Rows)
125 { 113 {
126 updatedRow.Operation = RowOperation.Add; 114 updatedRow.Operation = RowOperation.Add;
127 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; 115 updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId;
128 addedTable.Rows.Add(updatedRow); 116 addedTable.Rows.Add(updatedRow);
129 } 117 }
130 } 118 }
@@ -152,27 +140,8 @@ namespace WixToolset.Core.WindowsInstaller
152 140
153 if (null != primaryKey) 141 if (null != primaryKey)
154 { 142 {
155 if (index.TryGetValue(primaryKey, out var collisionRow)) 143 if (index.ContainsKey(primaryKey))
156 { 144 {
157#if TODO_PATCH // This case doesn't seem like it can happen any longer.
158 // Overriding WixActionRows have a primary key defined and take precedence in the index.
159 if (row is WixActionRow actionRow)
160 {
161 // If the current row is not overridable, see if the indexed row is.
162 if (!actionRow.Overridable)
163 {
164 if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable)
165 {
166 // The indexed key is overridable and should be replaced.
167 index[primaryKey] = actionRow;
168 }
169 }
170
171 // If we got this far, the row does not need to be indexed.
172 return;
173 }
174#endif
175
176 if (this.ShowPedanticMessages) 145 if (this.ShowPedanticMessages)
177 { 146 {
178 this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); 147 this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name));
@@ -208,7 +177,7 @@ namespace WixToolset.Core.WindowsInstaller
208 else if (null == updatedRow) 177 else if (null == updatedRow)
209 { 178 {
210 targetRow.Operation = RowOperation.Delete; 179 targetRow.Operation = RowOperation.Delete;
211 targetRow.SectionId += sectionDelimiter; 180 targetRow.SectionId += SectionDelimiter;
212 181
213 comparedRow = targetRow; 182 comparedRow = targetRow;
214 keepRow = true; 183 keepRow = true;
@@ -219,10 +188,10 @@ namespace WixToolset.Core.WindowsInstaller
219 updatedRow.Operation = RowOperation.None; 188 updatedRow.Operation = RowOperation.None;
220 if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) 189 if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
221 { 190 {
222 // ignore rows that shouldn't be in a transform 191 // Include only summary information rows that are allowed in a transform.
223 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) 192 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0]))
224 { 193 {
225 updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; 194 updatedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId;
226 comparedRow = updatedRow; 195 comparedRow = updatedRow;
227 keepRow = true; 196 keepRow = true;
228 } 197 }
@@ -273,14 +242,14 @@ namespace WixToolset.Core.WindowsInstaller
273 updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; 242 updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex;
274 updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; 243 updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri;
275 244
276 // always keep a copy of the previous data even if they are identical 245 // Always keep a copy of the previous data even if they are identical.
277 // This makes diff.wixmst clean and easier to control patch logic 246 // This makes diff data clean and easier to control in patch logic.
278 updatedObjectField.PreviousData = (string)targetObjectField.Data; 247 updatedObjectField.PreviousData = (string)targetObjectField.Data;
279 248
280 // always remember the unresolved data for target build 249 // Always remember the unresolved data for target build.
281 updatedObjectField.UnresolvedPreviousData = targetObjectField.UnresolvedData; 250 updatedObjectField.UnresolvedPreviousData = targetObjectField.UnresolvedData;
282 251
283 // keep rows containing object fields so the files can be compared in the binder 252 // Keep rows containing object fields so the files can be compared later.
284 keepRow = !this.SuppressKeepingSpecialRows; 253 keepRow = !this.SuppressKeepingSpecialRows;
285 } 254 }
286 else 255 else
@@ -305,7 +274,7 @@ namespace WixToolset.Core.WindowsInstaller
305 if (keepRow) 274 if (keepRow)
306 { 275 {
307 comparedRow = updatedRow; 276 comparedRow = updatedRow;
308 comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; 277 comparedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId;
309 } 278 }
310 } 279 }
311 } 280 }
@@ -333,9 +302,6 @@ namespace WixToolset.Core.WindowsInstaller
333 } 302 }
334 else // possibly modified table. 303 else // possibly modified table.
335 { 304 {
336 var updatedPrimaryKeys = new Dictionary<string, Row>();
337 var targetPrimaryKeys = new Dictionary<string, Row>();
338
339 // compare the table definitions 305 // compare the table definitions
340 if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) 306 if (0 != targetTable.Definition.CompareTo(updatedTable.Definition))
341 { 307 {
@@ -344,6 +310,9 @@ namespace WixToolset.Core.WindowsInstaller
344 } 310 }
345 else 311 else
346 { 312 {
313 var updatedPrimaryKeys = new Dictionary<string, Row>();
314 var targetPrimaryKeys = new Dictionary<string, Row>();
315
347 this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); 316 this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys);
348 317
349 // diff the target and updated rows 318 // diff the target and updated rows
@@ -371,7 +340,7 @@ namespace WixToolset.Core.WindowsInstaller
371 var updatedRow = updatedPrimaryKeyEntry.Value; 340 var updatedRow = updatedPrimaryKeyEntry.Value;
372 341
373 updatedRow.Operation = RowOperation.Add; 342 updatedRow.Operation = RowOperation.Add;
374 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; 343 updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId;
375 rows.Add(updatedRow); 344 rows.Add(updatedRow);
376 } 345 }
377 } 346 }
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
index 949d5e18..fa072293 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
@@ -2,9 +2,7 @@
2 2
3namespace WixToolset.Core.WindowsInstaller.Bind 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System;
6 using System.Collections.Generic; 5 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq; 6 using System.Linq;
9 using WixToolset.Data; 7 using WixToolset.Data;
10 using WixToolset.Data.Symbols; 8 using WixToolset.Data.Symbols;
@@ -29,20 +27,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind
29 { 27 {
30 var facades = new List<IFileFacade>(); 28 var facades = new List<IFileFacade>();
31 29
32 var assemblyFile = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id);
33#if TODO_PATCHING_DELTA 30#if TODO_PATCHING_DELTA
34 //var deltaPatchFiles = this.Section.Symbols.OfType<WixDeltaPatchFileSymbol>().ToDictionary(t => t.Id.Id); 31 //var deltaPatchFiles = this.Section.Symbols.OfType<WixDeltaPatchFileSymbol>().ToDictionary(t => t.Id.Id);
35#endif 32#endif
36 33
37 foreach (var file in this.Section.Symbols.OfType<FileSymbol>()) 34 foreach (var file in this.Section.Symbols.OfType<FileSymbol>())
38 { 35 {
39 assemblyFile.TryGetValue(file.Id.Id, out var assembly);
40
41#if TODO_PATCHING_DELTA 36#if TODO_PATCHING_DELTA
42 //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile); 37 //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile);
43 // TODO: should we be passing along delta information to the file facade? Probably, right? 38 // TODO: should we be passing along delta information to the file facade? Probably, right?
44#endif 39#endif
45 var fileFacade = this.BackendHelper.CreateFileFacade(file, assembly); 40 var fileFacade = this.BackendHelper.CreateFileFacade(file);
46 41
47 facades.Add(fileFacade); 42 facades.Add(fileFacade);
48 } 43 }
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs
index 441038ec..7c71e238 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs
@@ -37,8 +37,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
37 37
38 var patchMediaFileRows = new Dictionary<int, RowDictionary<FileRow>>(); 38 var patchMediaFileRows = new Dictionary<int, RowDictionary<FileRow>>();
39 39
40 //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
41
42 // Index paired transforms by name without their "#" prefix. 40 // Index paired transforms by name without their "#" prefix.
43 var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith(PatchConstants.PairedPatchTransformPrefix)).ToDictionary(s => s.Name, s => s.Data); 41 var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith(PatchConstants.PairedPatchTransformPrefix)).ToDictionary(s => s.Name, s => s.Data);
44 42
@@ -57,7 +55,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
57 var pairedTransform = pairedTransforms[PatchConstants.PairedPatchTransformPrefix + substorage.Name]; 55 var pairedTransform = pairedTransforms[PatchConstants.PairedPatchTransformPrefix + substorage.Name];
58 var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]); 56 var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]);
59 57
60 foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete)) 58 foreach (var mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete).Cast<FileRow>())
61 { 59 {
62 var mainFileId = mainFileRow.File; 60 var mainFileId = mainFileRow.File;
63 61
@@ -89,8 +87,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
89 } 87 }
90 else 88 else
91 { 89 {
92 // TODO: should this entire condition be placed in the binder file manager? 90 if (
93 if (/*(0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&*/ 91#if TODO_PATCHING_DELTA
92 (0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&
93#endif
94 !this.FileSystemManager.CompareFiles(objectField.PreviousData, objectField.Data.ToString())) 94 !this.FileSystemManager.CompareFiles(objectField.PreviousData, objectField.Data.ToString()))
95 { 95 {
96 // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. 96 // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
@@ -133,11 +133,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
133 patchMediaFileRows.Add(diskId, mediaFileRows); 133 patchMediaFileRows.Add(diskId, mediaFileRows);
134 } 134 }
135 135
136 var patchFileRow = mediaFileRows.Get(mainFileId); 136 if (!mediaFileRows.TryGetValue(mainFileId, out var patchFileRow))
137
138 if (null == patchFileRow)
139 { 137 {
140 //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
141 patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers); 138 patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers);
142 mainFileRow.CopyTo(patchFileRow); 139 mainFileRow.CopyTo(patchFileRow);
143 140
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
index 7e6a7de0..5f21f496 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
@@ -10,7 +10,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
10 using System.Runtime.InteropServices; 10 using System.Runtime.InteropServices;
11 using System.Text; 11 using System.Text;
12 using System.Threading; 12 using System.Threading;
13 using WixToolset.Core.Native;
14 using WixToolset.Core.Native.Msi; 13 using WixToolset.Core.Native.Msi;
15 using WixToolset.Core.Native.Msm; 14 using WixToolset.Core.Native.Msm;
16 using WixToolset.Data; 15 using WixToolset.Data;
@@ -24,11 +23,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
24 /// </summary> 23 /// </summary>
25 internal class MergeModulesCommand 24 internal class MergeModulesCommand
26 { 25 {
27 public MergeModulesCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IFileFacade> fileFacades, IntermediateSection section, IEnumerable<string> suppressedTableNames, string outputPath, string intermediateFolder) 26 public MergeModulesCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IFileFacade> fileFacadesFromModule, IntermediateSection section, IEnumerable<string> suppressedTableNames, string outputPath, string intermediateFolder)
28 { 27 {
29 this.Messaging = messaging; 28 this.Messaging = messaging;
30 this.BackendHelper = backendHelper; 29 this.BackendHelper = backendHelper;
31 this.FileFacades = fileFacades; 30 this.FileFacadesFromModule = fileFacadesFromModule;
32 this.Section = section; 31 this.Section = section;
33 this.SuppressedTableNames = suppressedTableNames ?? Array.Empty<string>(); 32 this.SuppressedTableNames = suppressedTableNames ?? Array.Empty<string>();
34 this.OutputPath = outputPath; 33 this.OutputPath = outputPath;
@@ -39,7 +38,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
39 38
40 private IBackendHelper BackendHelper { get; } 39 private IBackendHelper BackendHelper { get; }
41 40
42 private IEnumerable<IFileFacade> FileFacades { get; } 41 private IEnumerable<IFileFacade> FileFacadesFromModule { get; }
43 42
44 private IntermediateSection Section { get; } 43 private IntermediateSection Section { get; }
45 44
@@ -280,13 +279,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
280 this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles()); 279 this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles());
281 using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) 280 using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
282 { 281 {
283 foreach (var file in this.FileFacades) 282 foreach (var file in this.FileFacadesFromModule)
284 { 283 {
285 if (!file.FromModule)
286 {
287 continue;
288 }
289
290 using (var record = new Record(1)) 284 using (var record = new Record(1))
291 { 285 {
292 record.SetString(1, file.Id); 286 record.SetString(1, file.Id);
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs
index 217609be..1f09a267 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs
@@ -47,28 +47,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
47 if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal)) 47 if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal))
48 { 48 {
49 propertySymbol.Value = this.BackendHelper.CreateGuid(); 49 propertySymbol.Value = this.BackendHelper.CreateGuid();
50
51#if TODO_PATCHING // Is this still necessary?
52 // Update the target ProductCode in any instance transforms.
53 foreach (SubStorage subStorage in this.Output.SubStorages)
54 {
55 Output subStorageOutput = subStorage.Data;
56 if (OutputType.Transform != subStorageOutput.Type)
57 {
58 continue;
59 }
60
61 Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"];
62 foreach (Row row in instanceSummaryInformationTable.Rows)
63 {
64 if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0))
65 {
66 row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value);
67 break;
68 }
69 }
70 }
71#endif
72 } 50 }
73 else if ("ProductLanguage" == propertySymbol.Id.Id) 51 else if ("ProductLanguage" == propertySymbol.Id.Id)
74 { 52 {
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
index 8043ffa8..f2f8e126 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
@@ -19,11 +19,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
19 /// </summary> 19 /// </summary>
20 internal class UpdateFileFacadesCommand 20 internal class UpdateFileFacadesCommand
21 { 21 {
22 public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable<IFileFacade> fileFacades, IEnumerable<IFileFacade> updateFileFacades, IDictionary<string, string> variableCache, bool overwriteHash) 22 public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable<IFileFacade> allFileFacades, IEnumerable<IFileFacade> updateFileFacades, IDictionary<string, string> variableCache, bool overwriteHash)
23 { 23 {
24 this.Messaging = messaging; 24 this.Messaging = messaging;
25 this.Section = section; 25 this.Section = section;
26 this.FileFacades = fileFacades; 26 this.AllFileFacades = allFileFacades;
27 this.UpdateFileFacades = updateFileFacades; 27 this.UpdateFileFacades = updateFileFacades;
28 this.VariableCache = variableCache; 28 this.VariableCache = variableCache;
29 this.OverwriteHash = overwriteHash; 29 this.OverwriteHash = overwriteHash;
@@ -33,7 +33,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
33 33
34 private IntermediateSection Section { get; } 34 private IntermediateSection Section { get; }
35 35
36 private IEnumerable<IFileFacade> FileFacades { get; } 36 private IEnumerable<IFileFacade> AllFileFacades { get; }
37 37
38 private IEnumerable<IFileFacade> UpdateFileFacades { get; } 38 private IEnumerable<IFileFacade> UpdateFileFacades { get; }
39 39
@@ -43,15 +43,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
43 43
44 public void Execute() 44 public void Execute()
45 { 45 {
46 var assemblySymbols = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id);
46 var assemblyNameSymbols = this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id); 47 var assemblyNameSymbols = this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id);
47 48
48 foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) 49 foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null))
49 { 50 {
50 this.UpdateFileFacade(file, assemblyNameSymbols); 51 this.UpdateFileFacade(file, assemblySymbols, assemblyNameSymbols);
51 } 52 }
52 } 53 }
53 54
54 private void UpdateFileFacade(IFileFacade facade, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols) 55 private void UpdateFileFacade(IFileFacade facade, Dictionary<string, AssemblySymbol> assemblySymbols, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols)
55 { 56 {
56 FileInfo fileInfo = null; 57 FileInfo fileInfo = null;
57 try 58 try
@@ -124,7 +125,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
124 // 125 //
125 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version 126 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
126 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. 127 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
127 if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) 128 if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal)))
128 { 129 {
129 this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); 130 this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id));
130 } 131 }
@@ -153,16 +154,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
153 } 154 }
154 } 155 }
155 156
156 if (null == facade.Hash) 157 // Remember the hash symbol for use later.
158 facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)
157 { 159 {
158 facade.Hash = this.Section.AddSymbol(new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)); 160 Options = 0,
159 } 161 HashPart1 = hash[0],
160 162 HashPart2 = hash[1],
161 facade.Hash.Options = 0; 163 HashPart3 = hash[2],
162 facade.Hash.HashPart1 = hash[0]; 164 HashPart4 = hash[3],
163 facade.Hash.HashPart2 = hash[1]; 165 };
164 facade.Hash.HashPart3 = hash[2];
165 facade.Hash.HashPart4 = hash[3];
166 } 166 }
167 } 167 }
168 else // update the file row with the version and language information. 168 else // update the file row with the version and language information.
@@ -173,7 +173,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
173 { 173 {
174 facade.Version = version; 174 facade.Version = version;
175 } 175 }
176 else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. 176 else if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
177 { 177 {
178 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching 178 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
179 // the version value). We didn't find it so, we will override the default version they provided with the actual 179 // the version value). We didn't find it so, we will override the default version they provided with the actual
@@ -204,108 +204,104 @@ namespace WixToolset.Core.WindowsInstaller.Bind
204 this.VariableCache[$"filelanguage.{facade.Id}"] = facade.Language ?? String.Empty; 204 this.VariableCache[$"filelanguage.{facade.Id}"] = facade.Language ?? String.Empty;
205 } 205 }
206 206
207 // If this is a CLR assembly, load the assembly and get the assembly name information 207 // If there is an assembly for this file.
208 if (AssemblyType.DotNetAssembly == facade.AssemblyType) 208 if (assemblySymbols.TryGetValue(facade.Id, out var assemblySymbol))
209 { 209 {
210 try 210 // If this is a CLR assembly, load the assembly and get the assembly name information
211 if (AssemblyType.DotNetAssembly == assemblySymbol.Type)
211 { 212 {
212 var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); 213 try
214 {
215 var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version);
213 216
214 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); 217 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name);
215 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "culture", assemblyName.Culture); 218 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "culture", assemblyName.Culture);
216 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); 219 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version);
217 220
218 if (!String.IsNullOrEmpty(assemblyName.Architecture)) 221 if (!String.IsNullOrEmpty(assemblyName.Architecture))
219 { 222 {
220 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); 223 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture);
221 } 224 }
222 // TODO: WiX v3 seemed to do this but not clear it should actually be done. 225 // TODO: WiX v3 seemed to do this but not clear it should actually be done.
223 //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) 226 //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
224 //{ 227 //{
225 // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); 228 // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
226 //} 229 //}
227 230
228 if (assemblyName.StrongNamedSigned) 231 if (assemblyName.StrongNamedSigned)
229 { 232 {
230 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); 233 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
231 } 234 }
232 else if (facade.AssemblyApplicationFileRef == null) 235 else if (assemblySymbol.ApplicationFileRef == null)
233 { 236 {
234 throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); 237 throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef));
235 } 238 }
236 239
237 if (!String.IsNullOrEmpty(assemblyName.FileVersion)) 240 if (!String.IsNullOrEmpty(assemblyName.FileVersion))
238 { 241 {
239 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "fileVersion", assemblyName.FileVersion); 242 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "fileVersion", assemblyName.FileVersion);
240 } 243 }
241 244
242 // add the assembly name to the information cache 245 // add the assembly name to the information cache
243 if (null != this.VariableCache) 246 if (null != this.VariableCache)
247 {
248 this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName();
249 }
250 }
251 catch (WixException e)
244 { 252 {
245 this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); 253 this.Messaging.Write(e.Error);
246 } 254 }
247 } 255 }
248 catch (WixException e) 256 else if (AssemblyType.Win32Assembly == assemblySymbol.Type)
249 {
250 this.Messaging.Write(e.Error);
251 }
252 }
253 else if (AssemblyType.Win32Assembly == facade.AssemblyType)
254 {
255 // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through
256 // all files like this. Even though this is a rare case it looks like we might be able to index the
257 // file earlier.
258 var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal));
259 if (null == fileManifest)
260 {
261 this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef));
262 }
263
264 try
265 { 257 {
266 var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); 258 // TODO: Consider passing in the this.AllFileFacades as an indexed collection instead of searching through
267 259 // all files like this. Even though this is a rare case it looks like we might be able to index the
268 if (!String.IsNullOrEmpty(assemblyName.Name)) 260 // file earlier.
261 var fileManifest = this.AllFileFacades.FirstOrDefault(r => r.Id.Equals(assemblySymbol.ManifestFileRef, StringComparison.Ordinal));
262 if (null == fileManifest)
269 { 263 {
270 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); 264 this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, assemblySymbol.ManifestFileRef));
271 } 265 }
272 266
273 if (!String.IsNullOrEmpty(assemblyName.Version)) 267 try
274 { 268 {
275 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); 269 var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath);
276 }
277 270
278 if (!String.IsNullOrEmpty(assemblyName.Type)) 271 if (!String.IsNullOrEmpty(assemblyName.Name))
279 { 272 {
280 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "type", assemblyName.Type); 273 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name);
281 } 274 }
282 275
283 if (!String.IsNullOrEmpty(assemblyName.Architecture)) 276 if (!String.IsNullOrEmpty(assemblyName.Version))
284 { 277 {
285 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); 278 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version);
286 } 279 }
280
281 if (!String.IsNullOrEmpty(assemblyName.Type))
282 {
283 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "type", assemblyName.Type);
284 }
285
286 if (!String.IsNullOrEmpty(assemblyName.Architecture))
287 {
288 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture);
289 }
287 290
288 if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) 291 if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken))
292 {
293 this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
294 }
295 }
296 catch (WixException e)
289 { 297 {
290 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); 298 this.Messaging.Write(e.Error);
291 } 299 }
292 } 300 }
293 catch (WixException e)
294 {
295 this.Messaging.Write(e.Error);
296 }
297 } 301 }
298 } 302 }
299 303
300 /// <summary> 304 private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, IFileFacade facade, AssemblySymbol assemblySymbol, string name, string value)
301 /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise
302 /// create a new row.
303 /// </summary>
304 /// <param name="assemblyNameSymbols">MsiAssemblyName table.</param>
305 /// <param name="facade">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
306 /// <param name="name">MsiAssemblyName name.</param>
307 /// <param name="value">MsiAssemblyName value.</param>
308 private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, IFileFacade facade, string name, string value)
309 { 305 {
310 // check for null value (this can occur when grabbing the file version from an assembly without one) 306 // check for null value (this can occur when grabbing the file version from an assembly without one)
311 if (String.IsNullOrEmpty(value)) 307 if (String.IsNullOrEmpty(value))
@@ -315,36 +311,32 @@ namespace WixToolset.Core.WindowsInstaller.Bind
315 else 311 else
316 { 312 {
317 // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. 313 // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail.
318 if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType && 314 if ("name" == name && AssemblyType.DotNetAssembly == assemblySymbol.Type &&
319 String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) && 315 String.IsNullOrEmpty(assemblySymbol.ApplicationFileRef) &&
320 !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase)) 316 !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase))
321 { 317 {
322 this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value)); 318 this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value));
323 } 319 }
324 320
325 // override directly authored value 321 // Override directly authored value, otherwise remember the gathered information on the facade for use later.
326 var lookup = String.Concat(facade.ComponentRef, "/", name); 322 var lookup = String.Concat(facade.ComponentRef, "/", name);
327 if (!assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol)) 323 if (assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol))
328 { 324 {
329 assemblyNameSymbol = this.Section.AddSymbol(new MsiAssemblyNameSymbol(facade.SourceLineNumber, new Identifier(AccessModifier.Section, facade.ComponentRef, name)) 325 assemblyNameSymbol.Value = value;
326 }
327 else
328 {
329 assemblyNameSymbol = new MsiAssemblyNameSymbol(assemblySymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, facade.ComponentRef, name))
330 { 330 {
331 ComponentRef = facade.ComponentRef, 331 ComponentRef = facade.ComponentRef,
332 Name = name, 332 Name = name,
333 Value = value, 333 Value = value,
334 }); 334 };
335
336 if (null == facade.AssemblyNames)
337 {
338 facade.AssemblyNames = new List<MsiAssemblyNameSymbol>();
339 }
340
341 facade.AssemblyNames.Add(assemblyNameSymbol);
342 335
336 facade.AssemblyNameSymbols.Add(assemblyNameSymbol);
343 assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); 337 assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol);
344 } 338 }
345 339
346 assemblyNameSymbol.Value = value;
347
348 if (this.VariableCache != null) 340 if (this.VariableCache != null)
349 { 341 {
350 var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); 342 var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant();
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateSymbolsWithFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateSymbolsWithFileFacadesCommand.cs
new file mode 100644
index 00000000..4b332363
--- /dev/null
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateSymbolsWithFileFacadesCommand.cs
@@ -0,0 +1,73 @@
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 using System.Collections.Generic;
6 using System.Linq;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9 using WixToolset.Extensibility.Data;
10 using WixToolset.Extensibility.Services;
11
12 internal class UpdateSymbolsWithFileFacadesCommand
13 {
14 private readonly IntermediateSection section;
15 private readonly List<IFileFacade> allFileFacades;
16
17 public UpdateSymbolsWithFileFacadesCommand(IntermediateSection section, List<IFileFacade> allFileFacades)
18 {
19 this.section = section;
20 this.allFileFacades = allFileFacades;
21 }
22
23 public void Execute()
24 {
25 var fileSymbolsById = this.section.Symbols.OfType<FileSymbol>().ToDictionary(f => f.Id.Id);
26
27 foreach (var facade in this.allFileFacades)
28 {
29 if (fileSymbolsById.TryGetValue(facade.Id, out var fileSymbol))
30 {
31 // Only update the file symbol if the facade value changed
32 if (fileSymbol.DiskId != facade.DiskId)
33 {
34 fileSymbol.DiskId = facade.DiskId;
35 }
36
37 if (fileSymbol.FileSize != facade.FileSize)
38 {
39 fileSymbol.FileSize = facade.FileSize;
40 }
41
42 if (fileSymbol.Language != facade.Language)
43 {
44 fileSymbol.Language = facade.Language;
45 }
46
47 if (fileSymbol.Sequence != facade.Sequence)
48 {
49 fileSymbol.Sequence = facade.Sequence;
50 }
51
52 if (fileSymbol.Version != facade.Version)
53 {
54 fileSymbol.Version = facade.Version;
55 }
56 }
57
58 if (facade.MsiFileHashSymbol != null)
59 {
60 this.section.AddSymbol(facade.MsiFileHashSymbol);
61 }
62
63 if (facade.AssemblyNameSymbols != null)
64 {
65 foreach (var assemblyNameSymbol in facade.AssemblyNameSymbols)
66 {
67 this.section.AddSymbol(assemblyNameSymbol);
68 }
69 }
70 }
71 }
72 }
73}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs
index ed865450..7b6c21ef 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs
@@ -4,6 +4,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
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.Linq; 8 using System.Linq;
8 using WixToolset.Data; 9 using WixToolset.Data;
9 using WixToolset.Data.Symbols; 10 using WixToolset.Data.Symbols;
@@ -14,10 +15,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
14 15
15 internal class UpdateTransformsWithFileFacades 16 internal class UpdateTransformsWithFileFacades
16 { 17 {
17 public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable<SubStorage> subStorages, TableDefinitionCollection tableDefinitions, IEnumerable<IFileFacade> fileFacades) 18 public UpdateTransformsWithFileFacades(IMessaging messaging, IntermediateSection section, IEnumerable<SubStorage> subStorages, TableDefinitionCollection tableDefinitions, IEnumerable<IFileFacade> fileFacades)
18 { 19 {
19 this.Messaging = messaging; 20 this.Messaging = messaging;
20 this.Output = output; 21 this.Section = section;
21 this.SubStorages = subStorages; 22 this.SubStorages = subStorages;
22 this.TableDefinitions = tableDefinitions; 23 this.TableDefinitions = tableDefinitions;
23 this.FileFacades = fileFacades; 24 this.FileFacades = fileFacades;
@@ -25,7 +26,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
25 26
26 private IMessaging Messaging { get; } 27 private IMessaging Messaging { get; }
27 28
28 private WindowsInstallerData Output { get; } 29 private IntermediateSection Section { get; }
29 30
30 private IEnumerable<SubStorage> SubStorages { get; } 31 private IEnumerable<SubStorage> SubStorages { get; }
31 32
@@ -35,190 +36,97 @@ namespace WixToolset.Core.WindowsInstaller.Bind
35 36
36 public void Execute() 37 public void Execute()
37 { 38 {
38 var fileFacadesByDiskId = new Dictionary<int, Dictionary<string, IFileFacade>>(); 39 var fileFacadesByDiskId = this.IndexFileFacadesByDiskId();
39
40 // Index patch file facades by diskId+fileId.
41 foreach (var facade in this.FileFacades)
42 {
43 if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades))
44 {
45 mediaFacades = new Dictionary<string, IFileFacade>();
46 fileFacadesByDiskId.Add(facade.DiskId, mediaFacades);
47 }
48
49 mediaFacades.Add(facade.Id, facade);
50 }
51 40
52 var patchMediaRows = new RowDictionary<MediaRow>(this.Output.Tables["Media"]); 41 var mediaSymbolsByDiskId = this.Section.Symbols.OfType<MediaSymbol>().ToDictionary(m => m.DiskId);
53 42
54 // Index paired transforms by name without the "#" prefix. 43 // Index paired transforms by name without the "#" prefix.
55 var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith(PatchConstants.PairedPatchTransformPrefix)).ToDictionary(s => s.Name, s => s.Data); 44 var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith(PatchConstants.PairedPatchTransformPrefix)).ToDictionary(s => s.Name, s => s.Data);
56 45
57 // Copy File bind data into substorages
58 foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith(PatchConstants.PairedPatchTransformPrefix))) 46 foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith(PatchConstants.PairedPatchTransformPrefix)))
59 { 47 {
60 var mainTransform = substorage.Data; 48 var mainTransform = substorage.Data;
61 49
62 var mainMsiFileHashIndex = new RowDictionary<Row>(mainTransform.Tables["MsiFileHash"]);
63
64 var pairedTransform = pairedTransforms[PatchConstants.PairedPatchTransformPrefix + substorage.Name]; 50 var pairedTransform = pairedTransforms[PatchConstants.PairedPatchTransformPrefix + substorage.Name];
65 51
66 // Copy Media.LastSequence. 52 // Update the Media.LastSequence in the paired transforms.
67 var pairedMediaTable = pairedTransform.Tables["Media"]; 53 foreach (var pairedMediaRow in pairedTransform.Tables["Media"].Rows.Cast<MediaRow>())
68 foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
69 { 54 {
70 var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); 55 if (mediaSymbolsByDiskId.TryGetValue(pairedMediaRow.DiskId, out var mediaSymbol) && mediaSymbol.LastSequence.HasValue)
71 pairedMediaRow.LastSequence = patchMediaRow.LastSequence; 56 {
57 pairedMediaRow.LastSequence = mediaSymbol.LastSequence.Value;
58 }
59 else // TODO: This shouldn't be possible.
60 {
61 throw new InvalidDataException();
62 }
72 } 63 }
73 64
74 // Validate file row changes for keypath-related issues 65 // Validate file row changes for keypath-related issues
75 this.ValidateFileRowChanges(mainTransform); 66 this.ValidateFileRowChanges(mainTransform);
76 67
77 // Index File table of pairedTransform 68 // Copy File bind data into transforms
78 var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]); 69 if (mainTransform.Tables.TryGetTable("File", out var mainFileTable))
79
80 var mainFileTable = mainTransform.Tables["File"];
81 if (null != mainFileTable)
82 { 70 {
71 // Index File table of pairedTransform
72 var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]);
73
74 var mainMsiFileHashIndex = new RowDictionary<Row>(mainTransform.Tables["MsiFileHash"]);
75
83 // Remove the MsiFileHash table because it will be updated later with the final file hash for each file 76 // Remove the MsiFileHash table because it will be updated later with the final file hash for each file
84 mainTransform.Tables.Remove("MsiFileHash"); 77 mainTransform.Tables.Remove("MsiFileHash");
85 78
86 foreach (FileRow mainFileRow in mainFileTable.Rows) 79 foreach (var mainFileRow in mainFileTable.Rows.Where(r => r.Operation == RowOperation.Add || r.Operation == RowOperation.Modify).Cast<FileRow>())
87 { 80 {
88 if (RowOperation.Delete == mainFileRow.Operation) 81 // TODO: Wasn't this indexing done at the top of this method?
89 { 82 // Index main transform files by diskId+fileId
90 continue;
91 }
92 else if (RowOperation.None == mainFileRow.Operation)
93 {
94 continue;
95 }
96
97 // Index patch files by diskId+fileId
98 if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades)) 83 if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades))
99 { 84 {
100 mediaFacades = new Dictionary<string, IFileFacade>(); 85 mediaFacades = new Dictionary<string, IFileFacade>();
101 fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades); 86 fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades);
102 } 87 }
103 88
104 // copy data from the patch back to the transform 89 // Copy data from the facade back to the appropriate transform.
105 if (mediaFacades.TryGetValue(mainFileRow.File, out var facade)) 90 if (mediaFacades.TryGetValue(mainFileRow.File, out var facade))
106 { 91 {
107 var patchFileRow = facade.GetFileRow();
108 var pairedFileRow = pairedFileRows.Get(mainFileRow.File); 92 var pairedFileRow = pairedFileRows.Get(mainFileRow.File);
109 93
110 for (var i = 0; i < patchFileRow.Fields.Length; i++) 94 TryModifyField(mainFileRow, 3, facade.FileSize);
111 { 95
112 var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty; 96 TryModifyField(mainFileRow, 4, facade.Version);
113 var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty; 97
98 TryModifyField(mainFileRow, 5, facade.Language);
114 99
115 if (1 == i)
116 {
117 // File.Component_ changes should not come from the shared file rows
118 // that contain the file information as each individual transform might
119 // have different changes (or no changes at all).
120 }
121 else if (6 == i) // File.Attributes should not changed for binary deltas
122 {
123#if TODO_PATCHING_DELTA
124 if (null != patchFileRow.Patch)
125 {
126 // File.Attribute should not change for binary deltas
127 pairedFileRow.Attributes = mainFileRow.Attributes;
128 mainFileRow.Fields[i].Modified = false;
129 }
130#endif
131 }
132 else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform
133 {
134 // file sequence is updated in Patch table instead of File table for delta patches
135#if TODO_PATCHING_DELTA 100#if TODO_PATCHING_DELTA
136 if (null != patchFileRow.Patch) 101 // File.Attribute should not change for binary deltas, otherwise copy File Attributes from main transform row.
137 { 102 if (null != facade.Patch)
138 pairedFileRow.Fields[i].Modified = false;
139 }
140 else
141#endif 103#endif
142 { 104 {
143 pairedFileRow[i] = patchFileRow[i]; 105 TryModifyField(pairedFileRow, 6, mainFileRow.Attributes);
144 pairedFileRow.Fields[i].Modified = true; 106 mainFileRow.Fields[6].Modified = false;
145 }
146 mainFileRow.Fields[i].Modified = false;
147 }
148 else if (patchValue != mainValue)
149 {
150 mainFileRow[i] = patchFileRow[i];
151 mainFileRow.Fields[i].Modified = true;
152 if (mainFileRow.Operation == RowOperation.None)
153 {
154 mainFileRow.Operation = RowOperation.Modify;
155 }
156 }
157 } 107 }
158 108
159 // Copy MsiFileHash row for this File. 109#if TODO_PATCHING_DELTA
160 if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) 110 // File.Sequence is updated in Patch table instead of File table for delta patches
111 if (null != facade.Patch)
161 { 112 {
162 //patchHashRow = patchFileRow.Hash; 113 pairedFileRow.Fields[7].Modified = false;
163 throw new NotImplementedException();
164 } 114 }
165 115 else
166 if (null != patchHashRow) 116#endif
167 { 117 {
168 var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); 118 // File.Sequence is updated in pairedTransform, not mainTransform.
169 var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); 119 TryModifyField(pairedFileRow, 7, facade.Sequence);
170 for (var i = 0; i < patchHashRow.Fields.Length; i++)
171 {
172 mainHashRow[i] = patchHashRow[i];
173 if (i > 1)
174 {
175 // assume all hash fields have been modified
176 mainHashRow.Fields[i].Modified = true;
177 }
178 }
179
180 // assume the MsiFileHash operation follows the File one
181 mainHashRow.Operation = mainFileRow.Operation;
182 } 120 }
121 mainFileRow.Fields[7].Modified = false;
183 122
184 // copy MsiAssemblyName rows for this File 123 this.ProcessMsiFileHash(mainTransform, mainFileRow, facade.MsiFileHashSymbol, mainMsiFileHashIndex);
185#if TODO_PATCHING
186 List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
187 if (null != patchAssemblyNameRows)
188 {
189 var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
190 foreach (var patchAssemblyNameRow in patchAssemblyNameRows)
191 {
192 // Copy if there isn't an identical modified/added row already in the transform.
193 var foundMatchingModifiedRow = false;
194 foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows)
195 {
196 if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
197 {
198 foundMatchingModifiedRow = true;
199 break;
200 }
201 }
202 124
203 if (!foundMatchingModifiedRow) 125 this.ProcessMsiAssemblyName(mainTransform, mainFileRow, facade);
204 {
205 var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
206 for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
207 {
208 mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
209 }
210
211 // assume value field has been modified
212 mainAssemblyNameRow.Fields[2].Modified = true;
213 mainAssemblyNameRow.Operation = mainFileRow.Operation;
214 }
215 }
216 }
217#endif
218 126
219 // Add patch header for this file
220#if TODO_PATCHING_DELTA 127#if TODO_PATCHING_DELTA
221 if (null != patchFileRow.Patch) 128 // Add patch header for this file
129 if (null != facade.Patch)
222 { 130 {
223 // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. 131 // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
224 this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); 132 this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
@@ -232,12 +140,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
232 } 140 }
233 141
234 var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); 142 var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
235 patchRow[0] = patchFileRow.File; 143 patchRow[0] = facade.File;
236 patchRow[1] = patchFileRow.Sequence; 144 patchRow[1] = facade.Sequence;
237 145
238 var patchFile = new FileInfo(patchFileRow.Source); 146 var patchFile = new FileInfo(facade.Source);
239 patchRow[2] = (int)patchFile.Length; 147 patchRow[2] = (int)patchFile.Length;
240 patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; 148 patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & facade.PatchAttributes) ? 0 : 1;
241 149
242 var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; 150 var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
243 if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) 151 if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length)
@@ -252,13 +160,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
252 160
253 var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); 161 var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
254 patchHeadersRow[0] = streamName; 162 patchHeadersRow[0] = streamName;
255 patchHeadersRow[1] = patchFileRow.Patch; 163 patchHeadersRow[1] = facade.Patch;
256 patchRow[5] = streamName; 164 patchRow[5] = streamName;
257 patchHeadersRow.Operation = RowOperation.Add; 165 patchHeadersRow.Operation = RowOperation.Add;
258 } 166 }
259 else 167 else
260 { 168 {
261 patchRow[4] = patchFileRow.Patch; 169 patchRow[4] = facade.Patch;
262 } 170 }
263 patchRow.Operation = RowOperation.Add; 171 patchRow.Operation = RowOperation.Add;
264 } 172 }
@@ -270,14 +178,89 @@ namespace WixToolset.Core.WindowsInstaller.Bind
270 } 178 }
271 } 179 }
272 } 180 }
181 }
182 }
183
184 private void ProcessMsiFileHash(WindowsInstallerData transform, FileRow fileRow, MsiFileHashSymbol msiFileHashSymbol, RowDictionary<Row> msiFileHashIndex)
185 {
186 Row msiFileHashRow = null;
187
188 if (msiFileHashSymbol != null || msiFileHashIndex.TryGetValue(fileRow.File, out msiFileHashRow))
189 {
190 var sourceLineNumbers = msiFileHashSymbol?.SourceLineNumbers ?? msiFileHashRow?.SourceLineNumbers;
191
192 var transformHashTable = transform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
193
194 var transformHashRow = transformHashTable.CreateRow(sourceLineNumbers);
195 transformHashRow.Operation = fileRow.Operation; // Assume the MsiFileHash operation follows the File one.
196
197 transformHashRow[0] = fileRow.File;
198 transformHashRow[1] = msiFileHashSymbol?.Options ?? msiFileHashRow?.Fields[1].Data;
199
200 // Assume all hash fields have been modified.
201 TryModifyField(transformHashRow, 2, msiFileHashSymbol?.HashPart1 ?? msiFileHashRow?.Fields[2].Data);
202 TryModifyField(transformHashRow, 3, msiFileHashSymbol?.HashPart2 ?? msiFileHashRow?.Fields[3].Data);
203 TryModifyField(transformHashRow, 4, msiFileHashSymbol?.HashPart3 ?? msiFileHashRow?.Fields[4].Data);
204 TryModifyField(transformHashRow, 5, msiFileHashSymbol?.HashPart4 ?? msiFileHashRow?.Fields[5].Data);
205 }
206 }
273 207
274 this.Output.Tables.Remove("Media"); 208 private void ProcessMsiAssemblyName(WindowsInstallerData transform, FileRow fileRow, IFileFacade facade)
275 this.Output.Tables.Remove("File"); 209 {
276 this.Output.Tables.Remove("MsiFileHash"); 210 if (facade.AssemblyNameSymbols.Count > 0)
277 this.Output.Tables.Remove("MsiAssemblyName"); 211 {
212 var assemblyNameTable = transform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
213
214 foreach (var assemblyNameSymbol in facade.AssemblyNameSymbols)
215 {
216 // Copy if there isn't an identical modified/added row already in the transform.
217 var foundMatchingModifiedRow = false;
218 foreach (var mainAssemblyNameRow in assemblyNameTable.Rows.Where(r => r.Operation != RowOperation.None))
219 {
220 var component = mainAssemblyNameRow.FieldAsString(0);
221 var name = mainAssemblyNameRow.FieldAsString(1);
222
223 if (assemblyNameSymbol.ComponentRef == component && assemblyNameSymbol.Name == name)
224 {
225 foundMatchingModifiedRow = true;
226 break;
227 }
228 }
229
230 if (!foundMatchingModifiedRow)
231 {
232 var assemblyNameRow = assemblyNameTable.CreateRow(fileRow.SourceLineNumbers);
233 assemblyNameRow[0] = assemblyNameSymbol.ComponentRef;
234 assemblyNameRow[1] = assemblyNameSymbol.Name;
235 assemblyNameRow[2] = assemblyNameSymbol.Value;
236
237 // assume value field has been modified
238 assemblyNameRow.Fields[2].Modified = true;
239 assemblyNameRow.Operation = fileRow.Operation;
240 }
241 }
278 } 242 }
279 } 243 }
280 244
245 private Dictionary<int, Dictionary<string, IFileFacade>> IndexFileFacadesByDiskId()
246 {
247 var fileFacadesByDiskId = new Dictionary<int, Dictionary<string, IFileFacade>>();
248
249 // Index patch file facades by diskId+fileId.
250 foreach (var facade in this.FileFacades)
251 {
252 if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades))
253 {
254 mediaFacades = new Dictionary<string, IFileFacade>();
255 fileFacadesByDiskId.Add(facade.DiskId, mediaFacades);
256 }
257
258 mediaFacades.Add(facade.Id, facade);
259 }
260
261 return fileFacadesByDiskId;
262 }
263
281 /// <summary> 264 /// <summary>
282 /// Adds the PatchFiles action to the sequence table if it does not already exist. 265 /// Adds the PatchFiles action to the sequence table if it does not already exist.
283 /// </summary> 266 /// </summary>
@@ -349,6 +332,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind
349 } 332 }
350 } 333 }
351 334
335 private static bool TryModifyField(Row row, int index, object value)
336 {
337 var field = row.Fields[index];
338
339 if (field.Data != value)
340 {
341 field.Data = value;
342 field.Modified = true;
343
344 if (row.Operation == RowOperation.None)
345 {
346 row.Operation = RowOperation.Modify;
347 }
348 }
349
350 return field.Modified;
351 }
352
352 /// <summary> 353 /// <summary>
353 /// Tests sequence table for PatchFiles and associated actions 354 /// Tests sequence table for PatchFiles and associated actions
354 /// </summary> 355 /// </summary>
@@ -396,33 +397,29 @@ namespace WixToolset.Core.WindowsInstaller.Bind
396 return; 397 return;
397 } 398 }
398 399
399 var componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
400
401 // Index the Component table for non-directory & non-registry key paths. 400 // Index the Component table for non-directory & non-registry key paths.
402 foreach (var row in componentTable.Rows) 401 var componentKeyPath = new Dictionary<string, string>();
402 foreach (var row in componentTable.Rows.Cast<ComponentRow>().Where(r => !r.IsRegistryKeyPath))
403 { 403 {
404 var keyPath = row.FieldAsString(5); 404 var keyPath = row.KeyPath;
405 if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) 405
406 if (!String.IsNullOrEmpty(keyPath))
406 { 407 {
407 componentKeyPath.Add(row.FieldAsString(0), keyPath); 408 componentKeyPath.Add(row.Component, keyPath);
408 } 409 }
409 } 410 }
410 411
411 var componentWithChangedKeyPath = new Dictionary<string, string>(); 412 var componentWithChangedKeyPath = new Dictionary<string, string>();
412 var componentWithNonKeyPathChanged = new Dictionary<string, string>(); 413 var componentWithNonKeyPathChanged = new Dictionary<string, string>();
414
413 // Verify changes in the file table, now that file diffing has occurred 415 // Verify changes in the file table, now that file diffing has occurred
414 foreach (FileRow row in fileTable.Rows) 416 foreach (var row in fileTable.Rows.Cast<FileRow>().Where(r => r.Operation == RowOperation.Modify))
415 { 417 {
416 if (RowOperation.Modify != row.Operation) 418 var fileId = row.File;
417 { 419 var componentId = row.Component;
418 continue;
419 }
420
421 var fileId = row.FieldAsString(0);
422 var componentId = row.FieldAsString(1);
423 420
424 // If this file is the keypath of a component 421 // If this file is the keypath of a component
425 if (componentKeyPath.ContainsValue(fileId)) 422 if (componentKeyPath.ContainsValue(componentId))
426 { 423 {
427 if (!componentWithChangedKeyPath.ContainsKey(componentId)) 424 if (!componentWithChangedKeyPath.ContainsKey(componentId))
428 { 425 {
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs
index 727e20b2..68cb6554 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/TransformSubcommand.cs
@@ -21,6 +21,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
21 { 21 {
22 this.Messaging = serviceProvider.GetService<IMessaging>(); 22 this.Messaging = serviceProvider.GetService<IMessaging>();
23 this.BackendHelper = serviceProvider.GetService<IBackendHelper>(); 23 this.BackendHelper = serviceProvider.GetService<IBackendHelper>();
24 this.PathResolver = serviceProvider.GetService<IPathResolver>();
24 this.ExtensionManager = serviceProvider.GetService<IExtensionManager>(); 25 this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
25 } 26 }
26 27
@@ -28,6 +29,8 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
28 29
29 private IBackendHelper BackendHelper { get; } 30 private IBackendHelper BackendHelper { get; }
30 31
32 private IPathResolver PathResolver { get; }
33
31 private IExtensionManager ExtensionManager { get; } 34 private IExtensionManager ExtensionManager { get; }
32 35
33 private string OutputPath { get; set; } 36 private string OutputPath { get; set; }
@@ -40,8 +43,6 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
40 43
41 private string IntermediateFolder { get; set; } 44 private string IntermediateFolder { get; set; }
42 45
43 private bool IsAdminImage { get; set; }
44
45 private bool PreserveUnchangedRows { get; set; } 46 private bool PreserveUnchangedRows { get; set; }
46 47
47 private bool ShowPedanticMessages { get; set; } 48 private bool ShowPedanticMessages { get; set; }
@@ -133,10 +134,6 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
133 var parameter = argument.Substring(1); 134 var parameter = argument.Substring(1);
134 switch (parameter.ToLowerInvariant()) 135 switch (parameter.ToLowerInvariant())
135 { 136 {
136 case "a":
137 this.IsAdminImage = true;
138 return true;
139
140 case "intermediatefolder": 137 case "intermediatefolder":
141 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(argument); 138 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(argument);
142 return true; 139 return true;
@@ -194,15 +191,15 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
194 } 191 }
195 } 192 }
196 193
197 case "val": 194 case "t":
198 { 195 {
199 var val = parser.GetNextArgumentOrError(argument); 196 var type = parser.GetNextArgumentOrError(argument);
200 if (String.IsNullOrEmpty(val)) 197 if (String.IsNullOrEmpty(type))
201 { 198 {
202 return true; 199 return true;
203 } 200 }
204 201
205 switch (val.ToLowerInvariant()) 202 switch (type.ToLowerInvariant())
206 { 203 {
207 case "language": 204 case "language":
208 this.ValidationFlags |= TransformFlags.LanguageTransformDefault; 205 this.ValidationFlags |= TransformFlags.LanguageTransformDefault;
@@ -216,6 +213,22 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
216 this.ValidationFlags |= TransformFlags.PatchTransformDefault; 213 this.ValidationFlags |= TransformFlags.PatchTransformDefault;
217 return true; 214 return true;
218 215
216 default:
217 parser.ReportErrorArgument(argument, ErrorMessages.IllegalCommandLineArgumentValue(argument, type, new[] { "language", "instance", "patch" }));
218 return true;
219 }
220 }
221
222 case "val":
223 {
224 var val = parser.GetNextArgumentOrError(argument);
225 if (String.IsNullOrEmpty(val))
226 {
227 return true;
228 }
229
230 switch (val.ToLowerInvariant())
231 {
219 case "g": 232 case "g":
220 this.ValidationFlags |= TransformFlags.ValidateUpgradeCode; 233 this.ValidationFlags |= TransformFlags.ValidateUpgradeCode;
221 return true; 234 return true;
@@ -261,7 +274,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
261 return true; 274 return true;
262 275
263 default: 276 default:
264 parser.ReportErrorArgument(argument, ErrorMessages.IllegalCommandLineArgumentValue(argument, val, new[] { "language", "instance", "patch", "g", "l", "r", "s", "t", "u", "v", "w", "x", "y", "z" })); 277 parser.ReportErrorArgument(argument, ErrorMessages.IllegalCommandLineArgumentValue(argument, val, new[] { "g", "l", "r", "s", "t", "u", "v", "w", "x", "y", "z" }));
265 return true; 278 return true;
266 } 279 }
267 } 280 }
@@ -297,7 +310,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
297 { 310 {
298 Exception exception; 311 Exception exception;
299 312
300 (transform, exception) = LoadWindowsInstallerDataSafely(this.TargetPath); 313 (transform, exception) = DataLoader.LoadWindowsInstallerDataSafely(this.TargetPath);
301 314
302 if (transform?.Type != OutputType.Transform) 315 if (transform?.Type != OutputType.Transform)
303 { 316 {
@@ -340,17 +353,9 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
340 353
341 private WindowsInstallerData CreateTransform() 354 private WindowsInstallerData CreateTransform()
342 { 355 {
343 if (!TryLoadWindowsInstallerData(this.TargetPath, out var targetOutput)) 356 var targetData = this.GetWindowsInstallerData(this.TargetPath);
344 {
345 var unbindCommand = new UnbindMsiOrMsmCommand(this.Messaging, this.BackendHelper, this.TargetPath, this.ExportBasePath, this.IntermediateFolder, this.IsAdminImage, suppressDemodularization: true, suppressExtractCabinets: true);
346 targetOutput = unbindCommand.Execute();
347 }
348 357
349 if (!TryLoadWindowsInstallerData(this.TargetPath, out var updatedOutput)) 358 var updatedData = this.GetWindowsInstallerData(this.UpdatedPath);
350 {
351 var unbindCommand = new UnbindMsiOrMsmCommand(this.Messaging, this.BackendHelper, this.UpdatedPath, this.ExportBasePath, this.IntermediateFolder, this.IsAdminImage, suppressDemodularization: true, suppressExtractCabinets: true);
352 updatedOutput = unbindCommand.Execute();
353 }
354 359
355 var differ = new Differ(this.Messaging) 360 var differ = new Differ(this.Messaging)
356 { 361 {
@@ -359,7 +364,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
359 SuppressKeepingSpecialRows = this.SuppressKeepingSpecialRows 364 SuppressKeepingSpecialRows = this.SuppressKeepingSpecialRows
360 }; 365 };
361 366
362 return differ.Diff(targetOutput, updatedOutput, this.ValidationFlags); 367 return differ.Diff(targetData, updatedData, this.ValidationFlags);
363 } 368 }
364 369
365 private TableDefinitionCollection GetTableDefinitions() 370 private TableDefinitionCollection GetTableDefinitions()
@@ -370,37 +375,15 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine
370 return loadTableDefinitions.Execute(); 375 return loadTableDefinitions.Execute();
371 } 376 }
372 377
373 private static bool TryLoadWindowsInstallerData(string path, out WindowsInstallerData data) 378 private WindowsInstallerData GetWindowsInstallerData(string path)
374 { 379 {
375 data = null; 380 if (!DataLoader.TryLoadWindowsInstallerData(path, out var data))
376
377 var extension = Path.GetExtension(path);
378
379 // If the path is _not_ obviously a Windows Installer database, let's try opening it as
380 // our own data file format.
381 if (!extension.Equals(".msi", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".msm", StringComparison.OrdinalIgnoreCase))
382 {
383 (data, _) = LoadWindowsInstallerDataSafely(path);
384 }
385
386 return data != null;
387 }
388
389 private static (WindowsInstallerData, Exception) LoadWindowsInstallerDataSafely(string path)
390 {
391 WindowsInstallerData data = null;
392 Exception exception = null;
393
394 try
395 {
396 data = WindowsInstallerData.Load(path);
397 }
398 catch (Exception e)
399 { 381 {
400 exception = e; 382 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.PathResolver, path, OutputType.Product, this.ExportBasePath, null, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: false);
383 data = unbindCommand.Execute();
401 } 384 }
402 385
403 return (data, exception); 386 return data;
404 } 387 }
405 } 388 }
406} 389}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs
index ae9492eb..f4e4a1fc 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs
@@ -10,7 +10,6 @@ namespace WixToolset.Core.WindowsInstaller
10 using WixToolset.Data; 10 using WixToolset.Data;
11 using WixToolset.Data.Symbols; 11 using WixToolset.Data.Symbols;
12 using WixToolset.Data.WindowsInstaller; 12 using WixToolset.Data.WindowsInstaller;
13 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Services; 13 using WixToolset.Extensibility.Services;
15 14
16 /// <summary> 15 /// <summary>
@@ -18,7 +17,7 @@ namespace WixToolset.Core.WindowsInstaller
18 /// </summary> 17 /// </summary>
19 public sealed class Differ 18 public sealed class Differ
20 { 19 {
21 private const char sectionDelimiter = '/'; 20 private const char SectionDelimiter = '/';
22 private readonly IMessaging messaging; 21 private readonly IMessaging messaging;
23 private SummaryInformationStreams transformSummaryInfo; 22 private SummaryInformationStreams transformSummaryInfo;
24 23
@@ -53,24 +52,16 @@ namespace WixToolset.Core.WindowsInstaller
53 /// </summary> 52 /// </summary>
54 /// <param name="targetOutput">The target output.</param> 53 /// <param name="targetOutput">The target output.</param>
55 /// <param name="updatedOutput">The updated output.</param> 54 /// <param name="updatedOutput">The updated output.</param>
56 /// <returns>The transform.</returns>
57 public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput)
58 {
59 return this.Diff(targetOutput, updatedOutput, 0);
60 }
61
62 /// <summary>
63 /// Creates a transform by diffing two outputs.
64 /// </summary>
65 /// <param name="targetOutput">The target output.</param>
66 /// <param name="updatedOutput">The updated output.</param>
67 /// <param name="validationFlags"></param> 55 /// <param name="validationFlags"></param>
68 /// <returns>The transform.</returns> 56 /// <returns>The transform.</returns>
69 public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, TransformFlags validationFlags) 57 public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, TransformFlags validationFlags)
70 { 58 {
71 var transform = new WindowsInstallerData(null); 59 var transform = new WindowsInstallerData(null)
72 transform.Type = OutputType.Transform; 60 {
73 transform.Codepage = updatedOutput.Codepage; 61 Type = OutputType.Transform,
62 Codepage = updatedOutput.Codepage
63 };
64
74 this.transformSummaryInfo = new SummaryInformationStreams(); 65 this.transformSummaryInfo = new SummaryInformationStreams();
75 66
76 // compare the codepages 67 // compare the codepages
@@ -120,7 +111,7 @@ namespace WixToolset.Core.WindowsInstaller
120 foreach (var updatedRow in updatedTable.Rows) 111 foreach (var updatedRow in updatedTable.Rows)
121 { 112 {
122 updatedRow.Operation = RowOperation.Add; 113 updatedRow.Operation = RowOperation.Add;
123 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; 114 updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId;
124 addedTable.Rows.Add(updatedRow); 115 addedTable.Rows.Add(updatedRow);
125 } 116 }
126 } 117 }
@@ -209,7 +200,7 @@ namespace WixToolset.Core.WindowsInstaller
209 else if (null == updatedRow) 200 else if (null == updatedRow)
210 { 201 {
211 operation = targetRow.Operation = RowOperation.Delete; 202 operation = targetRow.Operation = RowOperation.Delete;
212 targetRow.SectionId += sectionDelimiter; 203 targetRow.SectionId += SectionDelimiter;
213 comparedRow = targetRow; 204 comparedRow = targetRow;
214 keepRow = true; 205 keepRow = true;
215 } 206 }
@@ -222,7 +213,7 @@ namespace WixToolset.Core.WindowsInstaller
222 // ignore rows that shouldn't be in a transform 213 // ignore rows that shouldn't be in a transform
223 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) 214 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0]))
224 { 215 {
225 updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; 216 updatedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId;
226 comparedRow = updatedRow; 217 comparedRow = updatedRow;
227 keepRow = true; 218 keepRow = true;
228 operation = RowOperation.Modify; 219 operation = RowOperation.Modify;
@@ -306,7 +297,7 @@ namespace WixToolset.Core.WindowsInstaller
306 if (keepRow) 297 if (keepRow)
307 { 298 {
308 comparedRow = updatedRow; 299 comparedRow = updatedRow;
309 comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; 300 comparedRow.SectionId = targetRow.SectionId + SectionDelimiter + updatedRow.SectionId;
310 } 301 }
311 } 302 }
312 } 303 }
@@ -350,8 +341,9 @@ namespace WixToolset.Core.WindowsInstaller
350 // diff the target and updated rows 341 // diff the target and updated rows
351 foreach (var targetPrimaryKeyEntry in targetPrimaryKeys) 342 foreach (var targetPrimaryKeyEntry in targetPrimaryKeys)
352 { 343 {
353 var targetPrimaryKey = targetPrimaryKeyEntry.Key; 344 updatedPrimaryKeys.TryGetValue(targetPrimaryKeyEntry.Key, out var updatedRow);
354 var compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value, updatedPrimaryKeys[targetPrimaryKey], out var _, out var keepRow); 345
346 var compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value, updatedRow, out var _, out var keepRow);
355 347
356 if (keepRow) 348 if (keepRow)
357 { 349 {
@@ -369,7 +361,7 @@ namespace WixToolset.Core.WindowsInstaller
369 var updatedRow = (Row)updatedPrimaryKeyEntry.Value; 361 var updatedRow = (Row)updatedPrimaryKeyEntry.Value;
370 362
371 updatedRow.Operation = RowOperation.Add; 363 updatedRow.Operation = RowOperation.Add;
372 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; 364 updatedRow.SectionId = SectionDelimiter + updatedRow.SectionId;
373 rows.Add(updatedRow); 365 rows.Add(updatedRow);
374 } 366 }
375 } 367 }
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/FileFacade.cs b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/FileFacade.cs
new file mode 100644
index 00000000..eede6e24
--- /dev/null
+++ b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/FileFacade.cs
@@ -0,0 +1,82 @@
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.ExtensibilityServices
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Data.Symbols;
8 using WixToolset.Data.WindowsInstaller;
9 using WixToolset.Data.WindowsInstaller.Rows;
10 using WixToolset.Extensibility.Data;
11
12 internal class FileFacade : IFileFacade
13 {
14 public FileFacade(FileSymbol file)
15 {
16 this.Identifier = file.Id;
17 this.ComponentRef = file.ComponentRef;
18 this.DiskId = file.DiskId ?? 1;
19 this.FileName = file.Name;
20 this.FileSize = file.FileSize;
21 this.Language = file.Language;
22 this.PatchGroup = file.PatchGroup;
23 this.Sequence = file.Sequence;
24 this.SourceLineNumber = file.SourceLineNumbers;
25 this.SourcePath = file.Source?.Path;
26 this.Compressed = (file.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed;
27 this.Uncompressed = (file.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed;
28 this.Version = file.Version;
29 this.AssemblyNameSymbols = new List<MsiAssemblyNameSymbol>();
30 }
31
32 public FileFacade(FileRow row)
33 {
34 this.Identifier = new Identifier(AccessModifier.Section, row.File);
35 this.ComponentRef = row.Component;
36 this.DiskId = row.DiskId;
37 this.FileName = row.FileName;
38 this.FileSize = row.FileSize;
39 this.Language = row.Language;
40 this.PatchGroup = null;
41 this.Sequence = row.Sequence;
42 this.SourceLineNumber = row.SourceLineNumbers;
43 this.SourcePath = row.Source;
44 this.Compressed = (row.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed;
45 this.Uncompressed = (row.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
46 this.Version = row.Version;
47 this.AssemblyNameSymbols = new List<MsiAssemblyNameSymbol>();
48 }
49
50 public string Id => this.Identifier.Id;
51
52 public Identifier Identifier { get; }
53
54 public string ComponentRef { get; }
55
56 public int DiskId { get; set; }
57
58 public string FileName { get; }
59
60 public int FileSize { get; set; }
61
62 public string Language { get; set; }
63
64 public int? PatchGroup { get; }
65
66 public int Sequence { get; set; }
67
68 public SourceLineNumber SourceLineNumber { get; }
69
70 public string SourcePath { get; }
71
72 public bool Compressed { get; }
73
74 public bool Uncompressed { get; }
75
76 public string Version { get; set; }
77
78 public MsiFileHashSymbol MsiFileHashSymbol { get; set; }
79
80 public ICollection<MsiAssemblyNameSymbol> AssemblyNameSymbols { get; }
81 }
82}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs
index ad738321..f372af82 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs
@@ -23,19 +23,14 @@ namespace WixToolset.Core.WindowsInstaller.ExtensibilityServices
23 23
24 #region IBackendHelper interfaces 24 #region IBackendHelper interfaces
25 25
26 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) 26 public IFileFacade CreateFileFacade(FileSymbol file)
27 { 27 {
28 return this.backendHelper.CreateFileFacade(file, assembly); 28 return new FileFacade(file);
29 } 29 }
30 30
31 public IFileFacade CreateFileFacade(FileRow fileRow) 31 public IFileFacade CreateFileFacade(FileRow fileRow)
32 { 32 {
33 return this.backendHelper.CreateFileFacade(fileRow); 33 return new FileFacade(fileRow);
34 }
35
36 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol)
37 {
38 return this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol);
39 } 34 }
40 35
41 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) 36 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null)
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs
index f73791aa..27bb282b 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs
@@ -2,11 +2,7 @@
2 2
3namespace WixToolset.Core.WindowsInstaller 3namespace WixToolset.Core.WindowsInstaller
4{ 4{
5 using System;
6 using WixToolset.Core.WindowsInstaller.Bind; 5 using WixToolset.Core.WindowsInstaller.Bind;
7 using WixToolset.Core.WindowsInstaller.Decompile;
8 using WixToolset.Core.WindowsInstaller.Unbind;
9 using WixToolset.Data;
10 using WixToolset.Extensibility; 6 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data; 7 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services; 8 using WixToolset.Extensibility.Services;
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
index 38a4ab34..bccdd3d4 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs
@@ -2,7 +2,6 @@
2 2
3namespace WixToolset.Core.WindowsInstaller 3namespace WixToolset.Core.WindowsInstaller
4{ 4{
5 using System;
6 using System.Collections.Generic; 5 using System.Collections.Generic;
7 using WixToolset.Core.WindowsInstaller.Bind; 6 using WixToolset.Core.WindowsInstaller.Bind;
8 using WixToolset.Data.WindowsInstaller; 7 using WixToolset.Data.WindowsInstaller;
@@ -18,8 +17,14 @@ namespace WixToolset.Core.WindowsInstaller
18 17
19 var backendHelper = context.ServiceProvider.GetService<IBackendHelper>(); 18 var backendHelper = context.ServiceProvider.GetService<IBackendHelper>();
20 19
20 var pathResolver = context.ServiceProvider.GetService<IPathResolver>();
21
22 var fileResolver = context.ServiceProvider.GetService<IFileResolver>();
23
21 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); 24 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
22 25
26 var resolveExtensions = extensionManager.GetServices<IResolverExtension>();
27
23 var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendBinderExtension>(); 28 var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendBinderExtension>();
24 29
25 foreach (var extension in backendExtensions) 30 foreach (var extension in backendExtensions)
@@ -30,7 +35,7 @@ namespace WixToolset.Core.WindowsInstaller
30 // Create transforms named in patch transforms. 35 // Create transforms named in patch transforms.
31 IEnumerable<PatchTransform> patchTransforms; 36 IEnumerable<PatchTransform> patchTransforms;
32 { 37 {
33 var command = new CreatePatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, context.IntermediateFolder); 38 var command = new CreatePatchTransformsCommand(messaging, backendHelper, pathResolver, fileResolver, resolveExtensions, context.IntermediateRepresentation, context.IntermediateFolder, context.BindPaths);
34 patchTransforms = command.Execute(); 39 patchTransforms = command.Execute();
35 } 40 }
36 41
@@ -42,7 +47,7 @@ namespace WixToolset.Core.WindowsInstaller
42 } 47 }
43 48
44 // Create WindowsInstallerData with patch metdata and transforms as sub-storages 49 // Create WindowsInstallerData with patch metdata and transforms as sub-storages
45 // Create MSP from WindowsInstallerData 50 // and create MSP from that WindowsInstallerData.
46 IBindResult result = null; 51 IBindResult result = null;
47 try 52 try
48 { 53 {
@@ -62,90 +67,5 @@ namespace WixToolset.Core.WindowsInstaller
62 throw; 67 throw;
63 } 68 }
64 } 69 }
65
66#if TODO_PATCHING
67 public Intermediate Unbind(IUnbindContext context)
68 {
69 Output patch;
70
71 // patch files are essentially database files (use a special flag to let the API know its a patch file)
72 try
73 {
74 using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile))
75 {
76 var unbindCommand = new UnbindDatabaseCommand(context.Messaging, database, context.InputFilePath, OutputType.Patch, context.ExportBasePath, context.IntermediateFolder, context.IsAdminImage, context.SuppressDemodularization, skipSummaryInfo: false);
77 patch = unbindCommand.Execute();
78 }
79 }
80 catch (Win32Exception e)
81 {
82 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
83 {
84 throw new WixException(WixErrors.OpenDatabaseFailed(context.InputFilePath));
85 }
86
87 throw;
88 }
89
90 // retrieve the transforms (they are in substorages)
91 using (Storage storage = Storage.Open(context.InputFilePath, StorageMode.Read | StorageMode.ShareDenyWrite))
92 {
93 Table summaryInformationTable = patch.Tables["_SummaryInformation"];
94 foreach (Row row in summaryInformationTable.Rows)
95 {
96 if (8 == (int)row[0]) // PID_LASTAUTHOR
97 {
98 string value = (string)row[1];
99
100 foreach (string decoratedSubStorageName in value.Split(';'))
101 {
102 string subStorageName = decoratedSubStorageName.Substring(1);
103 string transformFile = Path.Combine(context.IntermediateFolder, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst"));
104
105 // ensure the parent directory exists
106 Directory.CreateDirectory(Path.GetDirectoryName(transformFile));
107
108 // copy the substorage to a new storage for the transform file
109 using (Storage subStorage = storage.OpenStorage(subStorageName))
110 {
111 using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create))
112 {
113 subStorage.CopyTo(transformStorage);
114 }
115 }
116
117 // unbind the transform
118 var unbindCommand= new UnbindTransformCommand(context.Messaging, transformFile, (null == context.ExportBasePath ? null : Path.Combine(context.ExportBasePath, subStorageName)), context.IntermediateFolder);
119 var transform = unbindCommand.Execute();
120
121 patch.SubStorages.Add(new SubStorage(subStorageName, transform));
122 }
123
124 break;
125 }
126 }
127 }
128
129 // extract the files from the cabinets
130 // TODO: use per-transform export paths for support of multi-product patches
131 if (null != context.ExportBasePath && !context.SuppressExtractCabinets)
132 {
133 using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile))
134 {
135 foreach (SubStorage subStorage in patch.SubStorages)
136 {
137 // only patch transforms should carry files
138 if (subStorage.Name.StartsWith("#", StringComparison.Ordinal))
139 {
140 var extractCommand = new ExtractCabinetsCommand(subStorage.Data, database, context.InputFilePath, context.ExportBasePath, context.IntermediateFolder);
141 extractCommand.Execute();
142 }
143 }
144 }
145 }
146
147 return patch;
148 }
149#endif
150 } 70 }
151} 71}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs
index 8f52bed9..9aa6f3d4 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs
@@ -3,7 +3,6 @@
3namespace WixToolset.Core.WindowsInstaller.Unbind 3namespace WixToolset.Core.WindowsInstaller.Unbind
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.Globalization; 7 using System.Globalization;
9 using System.IO; 8 using System.IO;
@@ -26,7 +25,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
26 this.TreatOutputAsModule = treatOutputAsModule; 25 this.TreatOutputAsModule = treatOutputAsModule;
27 } 26 }
28 27
29 public string[] ExtractedFiles { get; private set; } 28 public Dictionary<string, MediaRow> ExtractedFileIdsWithMediaRow { get; private set; }
30 29
31 private WindowsInstallerData Output { get; } 30 private WindowsInstallerData Output { get; }
32 31
@@ -42,47 +41,51 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
42 41
43 public void Execute() 42 public void Execute()
44 { 43 {
44 var extractedFileIdsWithMediaRow = new Dictionary<string, MediaRow>();
45 var databaseBasePath = Path.GetDirectoryName(this.InputFilePath); 45 var databaseBasePath = Path.GetDirectoryName(this.InputFilePath);
46 var cabinetFiles = new List<string>(); 46
47 var embeddedCabinets = new SortedList(); 47 var cabinetPathsWithMediaRow = new Dictionary<string, MediaRow>();
48 var embeddedCabinetNamesByDiskId = new SortedDictionary<int, string>();
49 var embeddedCabinetRowsByDiskId = new SortedDictionary<int, MediaRow>();
48 50
49 // index all of the cabinet files 51 // index all of the cabinet files
50 if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule) 52 if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule)
51 { 53 {
52 embeddedCabinets.Add(0, "MergeModule.CABinet"); 54 embeddedCabinetNamesByDiskId.Add(0, "MergeModule.CABinet");
53 } 55 }
54 else if (null != this.Output.Tables["Media"]) 56 else if (this.Output.Tables.TryGetTable("Media", out var mediaTable))
55 { 57 {
56 foreach (MediaRow mediaRow in this.Output.Tables["Media"].Rows) 58 foreach (var mediaRow in mediaTable.Rows.Cast<MediaRow>().Where(r => !String.IsNullOrEmpty(r.Cabinet)))
57 { 59 {
58 if (null != mediaRow.Cabinet) 60 if (OutputType.Product == this.Output.Type ||
61 (OutputType.Transform == this.Output.Type && RowOperation.Add == mediaRow.Operation))
59 { 62 {
60 if (OutputType.Product == this.Output.Type || 63 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
61 (OutputType.Transform == this.Output.Type && RowOperation.Add == mediaRow.Operation))
62 { 64 {
63 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) 65 embeddedCabinetNamesByDiskId.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1));
64 { 66 embeddedCabinetRowsByDiskId.Add(mediaRow.DiskId, mediaRow);
65 embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1)); 67 }
66 } 68 else
67 else 69 {
68 { 70 cabinetPathsWithMediaRow.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet), mediaRow);
69 cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet));
70 }
71 } 71 }
72 } 72 }
73 } 73 }
74 } 74 }
75 75
76 // extract the embedded cabinet files from the database 76 // Extract any embedded cabinet files from the database.
77 if (0 < embeddedCabinets.Count) 77 if (0 < embeddedCabinetRowsByDiskId.Count)
78 { 78 {
79 using (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) 79 using (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?"))
80 { 80 {
81 foreach (int diskId in embeddedCabinets.Keys) 81 foreach (var diskIdWithCabinetName in embeddedCabinetNamesByDiskId)
82 { 82 {
83 var diskId = diskIdWithCabinetName.Key;
84 var cabinetName = diskIdWithCabinetName.Value;
85
83 using (var record = new Record(1)) 86 using (var record = new Record(1))
84 { 87 {
85 record.SetString(1, (string)embeddedCabinets[diskId]); 88 record.SetString(1, cabinetName);
86 streamsView.Execute(record); 89 streamsView.Execute(record);
87 } 90 }
88 91
@@ -92,15 +95,15 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
92 { 95 {
93 // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive, 96 // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive,
94 // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work 97 // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work
95 var cabinetFile = Path.Combine(this.IntermediateFolder, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); 98 var cabinetPath = Path.Combine(this.IntermediateFolder, "Media", diskId.ToString(CultureInfo.InvariantCulture), ".cab");
96 99
97 // ensure the parent directory exists 100 // ensure the parent directory exists
98 Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); 101 Directory.CreateDirectory(Path.GetDirectoryName(cabinetPath));
99 102
100 using (var fs = File.Create(cabinetFile)) 103 using (var fs = File.Create(cabinetPath))
101 { 104 {
102 int bytesRead; 105 int bytesRead;
103 var buffer = new byte[512]; 106 var buffer = new byte[4096];
104 107
105 while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length))) 108 while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length)))
106 { 109 {
@@ -108,7 +111,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
108 } 111 }
109 } 112 }
110 113
111 cabinetFiles.Add(cabinetFile); 114 embeddedCabinetRowsByDiskId.TryGetValue(diskId, out var cabinetMediaRow);
115 cabinetPathsWithMediaRow.Add(cabinetPath, cabinetMediaRow);
112 } 116 }
113 else 117 else
114 { 118 {
@@ -119,29 +123,34 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
119 } 123 }
120 } 124 }
121 125
122 // extract the cabinet files 126 // Extract files from any available cabinets.
123 if (0 < cabinetFiles.Count) 127 if (0 < cabinetPathsWithMediaRow.Count)
124 { 128 {
125 // ensure the directory exists or extraction will fail
126 Directory.CreateDirectory(this.ExportBasePath); 129 Directory.CreateDirectory(this.ExportBasePath);
127 130
128 foreach (var cabinetFile in cabinetFiles) 131 foreach (var cabinetPathWithMediaRow in cabinetPathsWithMediaRow)
129 { 132 {
133 var cabinetPath = cabinetPathWithMediaRow.Key;
134 var cabinetMediaRow = cabinetPathWithMediaRow.Value;
135
130 try 136 try
131 { 137 {
132 var cabinet = new Cabinet(cabinetFile); 138 var cabinet = new Cabinet(cabinetPath);
133 this.ExtractedFiles = cabinet.Extract(this.ExportBasePath).ToArray(); 139 var cabinetFilesExtracted = cabinet.Extract(this.ExportBasePath);
140
141 foreach (var extractedFile in cabinetFilesExtracted)
142 {
143 extractedFileIdsWithMediaRow.Add(extractedFile, cabinetMediaRow);
144 }
134 } 145 }
135 catch (FileNotFoundException) 146 catch (FileNotFoundException)
136 { 147 {
137 throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetFile)); 148 throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetPath));
138 } 149 }
139 } 150 }
140 } 151 }
141 else 152
142 { 153 this.ExtractedFileIdsWithMediaRow = extractedFileIdsWithMediaRow;
143 this.ExtractedFiles = new string[0];
144 }
145 } 154 }
146 } 155 }
147} 156}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
index c1fb7f12..7bbbbd76 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
@@ -5,29 +5,35 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
5 using System; 5 using System;
6 using System.Collections; 6 using System.Collections;
7 using System.Collections.Generic; 7 using System.Collections.Generic;
8 using System.ComponentModel;
8 using System.Globalization; 9 using System.Globalization;
9 using System.IO; 10 using System.IO;
11 using System.Linq;
10 using System.Text.RegularExpressions; 12 using System.Text.RegularExpressions;
11 using WixToolset.Core.Native.Msi; 13 using WixToolset.Core.Native.Msi;
12 using WixToolset.Data; 14 using WixToolset.Data;
13 using WixToolset.Data.WindowsInstaller; 15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Data.WindowsInstaller.Rows;
17 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services; 18 using WixToolset.Extensibility.Services;
15 19
16 internal class UnbindDatabaseCommand 20 internal class UnbindDatabaseCommand
17 { 21 {
18 private List<string> exportedFiles; 22 private static readonly Regex Modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}");
19 23
20 public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo) 24 private int sectionCount;
25
26 public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IPathResolver pathResolver, string databasePath, OutputType outputType, string exportBasePath, string extractFilesFolder, string intermediateFolder, bool enableDemodularization, bool skipSummaryInfo)
21 { 27 {
22 this.Messaging = messaging; 28 this.Messaging = messaging;
23 this.BackendHelper = backendHelper; 29 this.BackendHelper = backendHelper;
24 this.Database = database; 30 this.PathResolver = pathResolver;
25 this.DatabasePath = databasePath; 31 this.DatabasePath = databasePath;
26 this.OutputType = outputType; 32 this.OutputType = outputType;
27 this.ExportBasePath = exportBasePath; 33 this.ExportBasePath = exportBasePath;
34 this.ExtractFilesFolder = extractFilesFolder;
28 this.IntermediateFolder = intermediateFolder; 35 this.IntermediateFolder = intermediateFolder;
29 this.IsAdminImage = isAdminImage; 36 this.EnableDemodularization = enableDemodularization;
30 this.SuppressDemodularization = suppressDemodularization;
31 this.SkipSummaryInfo = skipSummaryInfo; 37 this.SkipSummaryInfo = skipSummaryInfo;
32 38
33 this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); 39 this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
@@ -37,7 +43,9 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
37 43
38 public IBackendHelper BackendHelper { get; } 44 public IBackendHelper BackendHelper { get; }
39 45
40 public Database Database { get; } 46 private IPathResolver PathResolver { get; }
47
48 private Database Database { get; set; }
41 49
42 public string DatabasePath { get; } 50 public string DatabasePath { get; }
43 51
@@ -45,73 +53,91 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
45 53
46 public string ExportBasePath { get; } 54 public string ExportBasePath { get; }
47 55
48 public string IntermediateFolder { get; } 56 public string ExtractFilesFolder { get; }
49 57
50 public bool IsAdminImage { get; } 58 public string IntermediateFolder { get; }
51 59
52 public bool SuppressDemodularization { get; } 60 public bool EnableDemodularization { get; }
53 61
54 public bool SkipSummaryInfo { get; } 62 public bool SkipSummaryInfo { get; }
55 63
56 public TableDefinitionCollection TableDefinitions { get; } 64 public TableDefinitionCollection TableDefinitions { get; }
57 65
58 public IEnumerable<string> ExportedFiles => this.exportedFiles; 66 public bool AdminImage { get; private set; }
59 67
60 private int SectionCount { get; set; } 68 public IEnumerable<string> ExportedFiles { get; private set; }
61 69
62 public WindowsInstallerData Execute() 70 public WindowsInstallerData Execute()
63 { 71 {
64 this.exportedFiles = new List<string>(); 72 var adminImage = false;
73 var exportedFiles = new List<string>();
65 74
66 string modularizationGuid = null; 75 var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath))
67 var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)); 76 {
68 View validationView = null; 77 Type = this.OutputType
78 };
79
80 try
81 {
82 using (var database = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
83 {
84 this.Database = database;
69 85
70 // set the output type 86 Directory.CreateDirectory(this.IntermediateFolder);
71 output.Type = this.OutputType;
72 87
73 Directory.CreateDirectory(this.IntermediateFolder); 88 output.Codepage = this.GetCodePage();
74 89
75 // get the codepage 90 var modularizationGuid = this.ProcessTables(output, exportedFiles);
76 this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt");
77 using (var sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt")))
78 {
79 string line;
80 91
81 while (null != (line = sr.ReadLine())) 92 var summaryInfo = this.ProcessSummaryInfo(output, modularizationGuid);
82 {
83 var data = line.Split('\t');
84 93
85 if (2 == data.Length) 94 this.UpdateUnrealFileColumns(this.DatabasePath, output, summaryInfo, exportedFiles);
86 { 95
87 output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); 96 this.GenerateSectionIds(output);
88 }
89 } 97 }
90 } 98 }
91 99 catch (Win32Exception e)
92 // get the summary information table if it exists; it won't if unbinding a transform
93 if (!this.SkipSummaryInfo)
94 { 100 {
95 using (var summaryInformation = new SummaryInformation(this.Database)) 101 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
96 { 102 {
97 var table = new Table(this.TableDefinitions["_SummaryInformation"]); 103 throw new WixException(ErrorMessages.OpenDatabaseFailed(this.DatabasePath));
104 }
98 105
99 for (var i = 1; 19 >= i; i++) 106 throw;
100 { 107 }
101 var value = summaryInformation.GetProperty(i);
102 108
103 if (0 < value.Length) 109 this.AdminImage = adminImage;
104 { 110 this.ExportedFiles = exportedFiles;
105 var row = table.CreateRow(output.SourceLineNumbers);
106 row[0] = i;
107 row[1] = value;
108 }
109 }
110 111
111 output.Tables.Add(table); 112 return output;
113 }
114
115 private int GetCodePage()
116 {
117 var codepage = 0;
118
119 this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt");
120
121 var lines = File.ReadAllLines(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt"));
122
123 if (lines.Length == 3)
124 {
125 var data = lines[2].Split('\t');
126
127 if (2 == data.Length)
128 {
129 codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture);
112 } 130 }
113 } 131 }
114 132
133 return codepage;
134 }
135
136 private string ProcessTables(WindowsInstallerData output, List<string> exportedFiles)
137 {
138 View validationView = null;
139 string modularizationGuid = null;
140
115 try 141 try
116 { 142 {
117 // open a view on the validation table if it exists 143 // open a view on the validation table if it exists
@@ -127,7 +153,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
127 { 153 {
128 var tableName = tableRecord.GetString(1); 154 var tableName = tableRecord.GetString(1);
129 155
130 using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) 156 using (var tableView = this.Database.OpenExecuteView($"SELECT * FROM `{tableName}`"))
131 { 157 {
132 var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView); 158 var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView);
133 var table = new Table(tableDefinition); 159 var table = new Table(tableDefinition);
@@ -154,16 +180,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
154 switch (row.Fields[i].Column.Type) 180 switch (row.Fields[i].Column.Type)
155 { 181 {
156 case ColumnType.Number: 182 case ColumnType.Number:
157 var success = false;
158 var intValue = rowRecord.GetInteger(i + 1); 183 var intValue = rowRecord.GetInteger(i + 1);
159 if (row.Fields[i].Column.IsLocalizable) 184 var success = row.Fields[i].Column.IsLocalizable ? row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)) : row.BestEffortSetField(i, intValue);
160 {
161 success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture));
162 }
163 else
164 {
165 success = row.BestEffortSetField(i, intValue);
166 }
167 185
168 if (!success) 186 if (!success)
169 { 187 {
@@ -171,20 +189,18 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
171 } 189 }
172 break; 190 break;
173 case ColumnType.Object: 191 case ColumnType.Object:
174 var sourceFile = "FILE NOT EXPORTED"; 192 var source = "FILE NOT EXPORTED";
175 193
176 if (null != this.ExportBasePath) 194 if (null != this.ExportBasePath)
177 { 195 {
178 var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); 196 source = Path.Combine(this.ExportBasePath, tableName, row.GetPrimaryKey('.'));
179 sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile);
180 197
181 // ensure the parent directory exists 198 Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName));
182 System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName));
183 199
184 using (var fs = System.IO.File.Create(sourceFile)) 200 using (var fs = File.Create(source))
185 { 201 {
186 int bytesRead; 202 int bytesRead;
187 var buffer = new byte[512]; 203 var buffer = new byte[4096];
188 204
189 while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) 205 while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length)))
190 { 206 {
@@ -192,10 +208,10 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
192 } 208 }
193 } 209 }
194 210
195 this.exportedFiles.Add(sourceFile); 211 exportedFiles.Add(source);
196 } 212 }
197 213
198 row[i] = sourceFile; 214 row[i] = source;
199 break; 215 break;
200 default: 216 default:
201 var value = rowRecord.GetString(i + 1); 217 var value = rowRecord.GetString(i + 1);
@@ -203,27 +219,26 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
203 switch (row.Fields[i].Column.Category) 219 switch (row.Fields[i].Column.Category)
204 { 220 {
205 case ColumnCategory.Guid: 221 case ColumnCategory.Guid:
206 value = value.ToUpper(CultureInfo.InvariantCulture); 222 value = value.ToUpperInvariant();
207 break; 223 break;
208 } 224 }
209 225
210 // de-modularize 226 // De-modularize
211 if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) 227 if (this.EnableDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType)
212 { 228 {
213 var modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}");
214
215 if (null == modularizationGuid) 229 if (null == modularizationGuid)
216 { 230 {
217 var match = modularization.Match(value); 231 var match = Modularization.Match(value);
218 if (match.Success) 232 if (match.Success)
219 { 233 {
220 modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); 234 modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}');
221 } 235 }
222 } 236 }
223 237
224 value = modularization.Replace(value, String.Empty); 238 value = Modularization.Replace(value, String.Empty);
225 } 239 }
226 240
241#if TODO_MOVE_TO_DECOMPILER
227 // escape "$(" for the preprocessor 242 // escape "$(" for the preprocessor
228 value = value.Replace("$(", "$$("); 243 value = value.Replace("$(", "$$(");
229 244
@@ -234,6 +249,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
234 //{ 249 //{
235 // value = value.Insert(matches[j].Index, "!"); 250 // value = value.Insert(matches[j].Index, "!");
236 //} 251 //}
252#endif
237 253
238 row[i] = value; 254 row[i] = value;
239 break; 255 break;
@@ -249,33 +265,54 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
249 } 265 }
250 finally 266 finally
251 { 267 {
252 if (null != validationView) 268 validationView?.Close();
253 {
254 validationView.Close();
255 }
256 } 269 }
257 270
258 // set the modularization guid as the PackageCode 271 return modularizationGuid;
259 if (null != modularizationGuid) 272 }
260 {
261 var table = output.Tables["_SummaryInformation"];
262 273
263 foreach (var row in table.Rows) 274 private SummaryInformationBits ProcessSummaryInfo(WindowsInstallerData output, string modularizationGuid)
275 {
276 var result = new SummaryInformationBits();
277
278 if (!this.SkipSummaryInfo)
279 {
280 using (var summaryInformation = new SummaryInformation(this.Database))
264 { 281 {
265 if (9 == (int)row[0]) // PID_REVNUMBER 282 var table = new Table(this.TableDefinitions["_SummaryInformation"]);
283
284 for (var i = 1; 19 >= i; i++)
266 { 285 {
267 row[1] = modularizationGuid; 286 var value = summaryInformation.GetProperty(i);
287
288 // Set the modularization guid as the PackageCode, for merge modules.
289 if (i == (int)SummaryInformation.Package.PackageCode && !String.IsNullOrEmpty(modularizationGuid))
290 {
291 var row = table.CreateRow(output.SourceLineNumbers);
292 row[0] = i;
293 row[1] = modularizationGuid;
294 }
295 else if (0 < value.Length)
296 {
297 var row = table.CreateRow(output.SourceLineNumbers);
298 row[0] = i;
299 row[1] = value;
300
301 if (i == (int)SummaryInformation.Package.FileAndElevatedFlags)
302 {
303 var wordcount = Convert.ToInt32(value, CultureInfo.InvariantCulture);
304 result.LongFilenames = (wordcount & 0x1) != 0x1;
305 result.Compressed = (wordcount & 0x2) == 0x2;
306 result.AdminImage = (wordcount & 0x4) == 0x4;
307 }
308 }
268 } 309 }
269 }
270 }
271 310
272 if (this.IsAdminImage) 311 output.Tables.Add(table);
273 { 312 }
274 this.GenerateWixFileTable(this.DatabasePath, output);
275 this.GenerateSectionIds(output);
276 } 313 }
277 314
278 return output; 315 return result;
279 } 316 }
280 317
281 private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView) 318 private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView)
@@ -310,10 +347,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
310 var columnName = columnNameRecord.GetString(i); 347 var columnName = columnNameRecord.GetString(i);
311 var idtType = columnTypeRecord.GetString(i); 348 var idtType = columnTypeRecord.GetString(i);
312 349
313 ColumnType columnType;
314 int length;
315 bool nullable;
316
317 var columnCategory = ColumnCategory.Unknown; 350 var columnCategory = ColumnCategory.Unknown;
318 var columnModularizeType = ColumnModularizeType.None; 351 var columnModularizeType = ColumnModularizeType.None;
319 var primary = tablePrimaryKeys.Contains(columnName); 352 var primary = tablePrimaryKeys.Contains(columnName);
@@ -326,7 +359,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
326 string description = null; 359 string description = null;
327 360
328 // get the column type, length, and whether its nullable 361 // get the column type, length, and whether its nullable
329 switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) 362 ColumnType columnType;
363 switch (Char.ToLowerInvariant(idtType[0]))
330 { 364 {
331 case 'i': 365 case 'i':
332 columnType = ColumnType.Number; 366 columnType = ColumnType.Number;
@@ -345,8 +379,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
345 columnType = ColumnType.Unknown; 379 columnType = ColumnType.Unknown;
346 break; 380 break;
347 } 381 }
348 length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); 382 var length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture);
349 nullable = Char.IsUpper(idtType[0]); 383 var nullable = Char.IsUpper(idtType[0]);
350 384
351 // try to get validation information 385 // try to get validation information
352 if (null != validationView) 386 if (null != validationView)
@@ -385,11 +419,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
385 // convert category to ColumnCategory 419 // convert category to ColumnCategory
386 if (null != category) 420 if (null != category)
387 { 421 {
388 try 422 if (!Enum.TryParse(category, true, out columnCategory))
389 {
390 columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true);
391 }
392 catch (ArgumentException)
393 { 423 {
394 columnCategory = ColumnCategory.Unknown; 424 columnCategory = ColumnCategory.Unknown;
395 } 425 }
@@ -427,67 +457,103 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
427 return new TableDefinition(tableName, null, columns, false); 457 return new TableDefinition(tableName, null, columns, false);
428 } 458 }
429 459
430 /// <summary> 460 private void UpdateUnrealFileColumns(string databaseFile, WindowsInstallerData output, SummaryInformationBits summaryInformation, List<string> exportedFiles)
431 /// Generates the WixFile table based on a path to an admin image msi and an Output.
432 /// </summary>
433 /// <param name="databaseFile">The path to the msi database file in an admin image.</param>
434 /// <param name="output">The Output that represents the msi database.</param>
435 private void GenerateWixFileTable(string databaseFile, WindowsInstallerData output)
436 { 461 {
437 throw new NotImplementedException(); 462 var fileRows = output.Tables["File"]?.Rows;
438#if TODO_FIX_UNBINDING_FILES
439 var adminRootPath = Path.GetDirectoryName(databaseFile);
440 463
441 var componentDirectoryIndex = new Hashtable(); 464 if (fileRows == null || fileRows.Count == 0)
442 var componentTable = output.Tables["Component"];
443 foreach (var row in componentTable.Rows)
444 { 465 {
445 componentDirectoryIndex.Add(row[0], row[2]); 466 return;
446 } 467 }
447 468
469 this.UpdateFileRowsDiskId(output, fileRows);
470
471 this.UpdateFileRowsSource(databaseFile, output, fileRows, summaryInformation, exportedFiles);
472 }
473
474 private void UpdateFileRowsDiskId(WindowsInstallerData output, IList<Row> fileRows)
475 {
476 var mediaRows = output.Tables["Media"]?.Rows?.Cast<MediaRow>()?.OrderBy(r => r.LastSequence)?.ToList();
477
478 var lastMediaRowIndex = 0;
479 var lastMediaRow = (mediaRows == null || mediaRows.Count == 0) ? null : mediaRows[lastMediaRowIndex];
480
481 foreach (var fileRow in fileRows.Cast<FileRow>()?.OrderBy(r => r.Sequence))
482 {
483 while (lastMediaRow != null && fileRow.Sequence > lastMediaRow.LastSequence)
484 {
485 ++lastMediaRowIndex;
486
487 lastMediaRow = lastMediaRowIndex < mediaRows.Count ? mediaRows[lastMediaRowIndex] : null;
488 }
489
490 fileRow.DiskId = lastMediaRow?.DiskId ?? 1;
491 }
492 }
493
494 private void UpdateFileRowsSource(string databasePath, WindowsInstallerData output, IList<Row> fileRows, SummaryInformationBits summaryInformation, List<string> exportedFiles)
495 {
496 var databaseFolder = Path.GetDirectoryName(databasePath);
497
498 var componentDirectoryIndex = output.Tables["Component"].Rows.Cast<ComponentRow>().ToDictionary(r => r.Component, r => r.Directory);
499
448 // Index full source paths for all directories 500 // Index full source paths for all directories
449 var directoryDirectoryParentIndex = new Hashtable(); 501 var directories = new Dictionary<string, IResolvedDirectory>();
450 var directoryFullPathIndex = new Hashtable(); 502
451 var directorySourceNameIndex = new Hashtable();
452 var directoryTable = output.Tables["Directory"]; 503 var directoryTable = output.Tables["Directory"];
453 foreach (var row in directoryTable.Rows) 504 foreach (var row in directoryTable.Rows)
454 { 505 {
455 directoryDirectoryParentIndex.Add(row[0], row[1]); 506 var sourceName = this.BackendHelper.GetMsiFileName(row.FieldAsString(2), source: true, longName: summaryInformation.LongFilenames);
456 if (null == row[1]) 507 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(row.FieldAsString(1), sourceName);
457 { 508
458 directoryFullPathIndex.Add(row[0], adminRootPath); 509 directories.Add(row.FieldAsString(0), resolvedDirectory);
459 }
460 else
461 {
462 directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2]));
463 }
464 } 510 }
465 511
466 foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) 512 if (summaryInformation.AdminImage)
467 { 513 {
468 if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) 514 foreach (var fileRow in fileRows.Cast<FileRow>())
469 { 515 {
470 this.GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); 516 var directoryId = componentDirectoryIndex[fileRow.Component];
517 var relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, directoryId, fileRow.FileName, compressed: false, useLongName: summaryInformation.LongFilenames);
518
519 fileRow.Source = Path.Combine(databaseFolder, relativeFileLayoutPath);
471 } 520 }
472 } 521 }
473 522 else
474 var fileTable = output.Tables["File"];
475 var wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]);
476 foreach (var row in fileTable.Rows)
477 { 523 {
478 var wixFileRow = new WixFileRow(null, this.TableDefinitions["WixFile"]); 524 var extractedFileIds = new HashSet<string>();
479 wixFileRow.File = (string)row[0];
480 wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]];
481 wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2]));
482 525
483 if (!File.Exists(wixFileRow.Source)) 526 if (!String.IsNullOrEmpty(this.ExtractFilesFolder))
484 { 527 {
485 throw new WixException(ErrorMessages.WixFileNotFound(wixFileRow.Source)); 528 var extractCommand = new ExtractCabinetsCommand(output, this.Database, this.DatabasePath, this.ExtractFilesFolder, this.IntermediateFolder);
529 extractCommand.Execute();
530
531 extractedFileIds = new HashSet<string>(extractCommand.ExtractedFileIdsWithMediaRow.Keys, StringComparer.OrdinalIgnoreCase);
532 exportedFiles.AddRange(extractedFileIds);
486 } 533 }
487 534
488 wixFileTable.Rows.Add(wixFileRow); 535 foreach (var fileRow in fileRows.Cast<FileRow>())
536 {
537 var source = "FILE NOT EXPORTED";
538
539 if (fileRow.Compressed == YesNoType.Yes || (fileRow.Compressed == YesNoType.NotSet && summaryInformation.Compressed))
540 {
541 if (extractedFileIds.Contains(fileRow.File))
542 {
543 source = Path.Combine(this.ExtractFilesFolder, fileRow.File);
544 }
545 }
546 else
547 {
548 var directoryId = componentDirectoryIndex[fileRow.Component];
549 var relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, directoryId, fileRow.FileName, compressed: false, useLongName: summaryInformation.LongFilenames);
550
551 source = Path.Combine(databaseFolder, relativeFileLayoutPath);
552 }
553
554 fileRow.Source = source;
555 }
489 } 556 }
490#endif
491 } 557 }
492 558
493 /// <summary> 559 /// <summary>
@@ -620,7 +686,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
620 { 686 {
621 switch (table.Name) 687 switch (table.Name)
622 { 688 {
623 case "WixFile":
624 case "MsiFileHash": 689 case "MsiFileHash":
625 ConnectTableToSection(table, fileSectionIdIndex, 0); 690 ConnectTableToSection(table, fileSectionIdIndex, 0);
626 break; 691 break;
@@ -699,7 +764,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
699 } 764 }
700 } 765 }
701 766
702 // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids. 767 // Now pass the output to each unbinder extension to allow them to analyze the output and determine their proper section ids.
703 //foreach (IUnbinderExtension extension in this.unbinderExtensions) 768 //foreach (IUnbinderExtension extension in this.unbinderExtensions)
704 //{ 769 //{
705 // extension.GenerateSectionIds(output); 770 // extension.GenerateSectionIds(output);
@@ -711,19 +776,22 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
711 /// </summary> 776 /// </summary>
712 /// <param name="table">The table to add sections to.</param> 777 /// <param name="table">The table to add sections to.</param>
713 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param> 778 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
714 /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns> 779 /// <returns>A dictionary containing the tables key for each row paired with its assigned section id.</returns>
715 private Hashtable AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex) 780 private Dictionary<string, string> AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex)
716 { 781 {
717 var hashtable = new Hashtable(); 782 var primaryKeyToSectionId = new Dictionary<string, string>();
783
718 if (null != table) 784 if (null != table)
719 { 785 {
720 foreach (var row in table.Rows) 786 foreach (var row in table.Rows)
721 { 787 {
722 row.SectionId = this.GetNewSectionId(); 788 row.SectionId = this.GetNewSectionId();
723 hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId); 789
790 primaryKeyToSectionId.Add(row.FieldAsString(rowPrimaryKeyIndex), row.SectionId);
724 } 791 }
725 } 792 }
726 return hashtable; 793
794 return primaryKeyToSectionId;
727 } 795 }
728 796
729 /// <summary> 797 /// <summary>
@@ -732,15 +800,15 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
732 /// <param name="table">The table containing rows that need to be connected to sections.</param> 800 /// <param name="table">The table containing rows that need to be connected to sections.</param>
733 /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param> 801 /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param>
734 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param> 802 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
735 private static void ConnectTableToSection(Table table, Hashtable sectionIdIndex, int rowIndex) 803 private static void ConnectTableToSection(Table table, Dictionary<string, string> sectionIdIndex, int rowIndex)
736 { 804 {
737 if (null != table) 805 if (null != table)
738 { 806 {
739 foreach (var row in table.Rows) 807 foreach (var row in table.Rows)
740 { 808 {
741 if (sectionIdIndex.ContainsKey(row[rowIndex])) 809 if (sectionIdIndex.TryGetValue(row.FieldAsString(rowIndex), out var sectionId))
742 { 810 {
743 row.SectionId = (string)sectionIdIndex[row[rowIndex]]; 811 row.SectionId = sectionId;
744 } 812 }
745 } 813 }
746 } 814 }
@@ -750,40 +818,53 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
750 /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it. 818 /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it.
751 /// </summary> 819 /// </summary>
752 /// <param name="table">The table containing rows that need to be connected to sections.</param> 820 /// <param name="table">The table containing rows that need to be connected to sections.</param>
753 /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param> 821 /// <param name="sectionIdIndex">A dictionary containing keys to map table to its section.</param>
754 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param> 822 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
755 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param> 823 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
756 /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns> 824 /// <returns>A dictionary containing the tables key for each row paired with its assigned section id.</returns>
757 private static Hashtable ConnectTableToSectionAndIndex(Table table, Hashtable sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex) 825 private static Dictionary<string, string> ConnectTableToSectionAndIndex(Table table, Dictionary<string, string> sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex)
758 { 826 {
759 var newHashTable = new Hashtable(); 827 var newPrimaryKeyToSectionId = new Dictionary<string, string>();
828
760 if (null != table) 829 if (null != table)
761 { 830 {
762 foreach (var row in table.Rows) 831 foreach (var row in table.Rows)
763 { 832 {
764 if (!sectionIdIndex.ContainsKey(row[rowIndex])) 833 var foreignKey = row.FieldAsString(rowIndex);
834
835 if (!sectionIdIndex.TryGetValue(foreignKey, out var sectionId))
765 { 836 {
766 continue; 837 continue;
767 } 838 }
768 839
769 row.SectionId = (string)sectionIdIndex[row[rowIndex]]; 840 row.SectionId = sectionId;
770 if (null != row[rowPrimaryKeyIndex]) 841
842 var primaryKey = row.FieldAsString(rowPrimaryKeyIndex);
843
844 if (!String.IsNullOrEmpty(primaryKey) && sectionIdIndex.ContainsKey(primaryKey))
771 { 845 {
772 newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId); 846 newPrimaryKeyToSectionId.Add(primaryKey, row.SectionId);
773 } 847 }
774 } 848 }
775 } 849 }
776 return newHashTable; 850
851 return newPrimaryKeyToSectionId;
777 } 852 }
778 853
779 /// <summary>
780 /// Creates a new section identifier to be used when adding a section to an output.
781 /// </summary>
782 /// <returns>A string representing a new section id.</returns>
783 private string GetNewSectionId() 854 private string GetNewSectionId()
784 { 855 {
785 this.SectionCount++; 856 this.sectionCount++;
786 return "wix.section." + this.SectionCount.ToString(CultureInfo.InvariantCulture); 857
858 return "wix.section." + this.sectionCount.ToString(CultureInfo.InvariantCulture);
859 }
860
861 private class SummaryInformationBits
862 {
863 public bool AdminImage { get; set; }
864
865 public bool Compressed { get; set; }
866
867 public bool LongFilenames { get; set; }
787 } 868 }
788 } 869 }
789} 870}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs
deleted file mode 100644
index 8070d42d..00000000
--- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs
+++ /dev/null
@@ -1,72 +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.Unbind
4{
5 using System;
6 using System.ComponentModel;
7 using WixToolset.Core.Native.Msi;
8 using WixToolset.Data;
9 using WixToolset.Data.WindowsInstaller;
10 using WixToolset.Extensibility.Services;
11
12 internal class UnbindMsiOrMsmCommand
13 {
14 public UnbindMsiOrMsmCommand(IMessaging messaging, IBackendHelper backendHelper, string databasePath, string exportBasePath, string intermediateFolder, bool adminImage, bool suppressDemodularization, bool suppressExtractCabinets)
15 {
16 this.Messaging = messaging;
17 this.BackendHelper = backendHelper;
18 this.DatabasePath = databasePath;
19 this.ExportBasePath = exportBasePath;
20 this.IntermediateFolder = intermediateFolder;
21 this.IsAdminImage = adminImage;
22 this.SuppressDemodularization = suppressDemodularization;
23 this.SuppressExtractCabinets = suppressExtractCabinets;
24 }
25
26 private IMessaging Messaging { get; }
27
28 private IBackendHelper BackendHelper { get; }
29
30 private string DatabasePath { get; }
31
32 private string ExportBasePath { get; }
33
34 private string IntermediateFolder { get; }
35
36 private bool IsAdminImage { get; }
37
38 private bool SuppressDemodularization { get; }
39
40 private bool SuppressExtractCabinets { get; }
41
42 public WindowsInstallerData Execute()
43 {
44 try
45 {
46 using (var database = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
47 {
48 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, this.DatabasePath, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, this.IsAdminImage, this.SuppressDemodularization, skipSummaryInfo: false);
49 var data = unbindCommand.Execute();
50
51 // extract the files from the cabinets
52 if (!String.IsNullOrEmpty(this.ExportBasePath) && !this.SuppressExtractCabinets)
53 {
54 var extractCommand = new ExtractCabinetsCommand(data, database, this.DatabasePath, this.ExportBasePath, this.IntermediateFolder);
55 extractCommand.Execute();
56 }
57
58 return data;
59 }
60 }
61 catch (Win32Exception e)
62 {
63 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
64 {
65 //throw new WixException(WixErrors.OpenDatabaseFailed(this.DatabasePath));
66 }
67
68 throw;
69 }
70 }
71 }
72}
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs
index 01ff1a80..bce60e40 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTransformCommand.cs
@@ -1,5 +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
3#if TODO_KEEP_FOR_PATCH_UNBINDING_CONSIDERATION_IN_FUTURE
4
3namespace WixToolset.Core.WindowsInstaller.Unbind 5namespace WixToolset.Core.WindowsInstaller.Unbind
4{ 6{
5 using System; 7 using System;
@@ -17,10 +19,11 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
17 19
18 internal class UnbindTransformCommand 20 internal class UnbindTransformCommand
19 { 21 {
20 public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, string transformFile, string exportBasePath, string intermediateFolder) 22 public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string transformFile, string exportBasePath, string intermediateFolder)
21 { 23 {
22 this.Messaging = messaging; 24 this.Messaging = messaging;
23 this.BackendHelper = backendHelper; 25 this.BackendHelper = backendHelper;
26 this.FileSystemManager = fileSystemManager;
24 this.TransformFile = transformFile; 27 this.TransformFile = transformFile;
25 this.ExportBasePath = exportBasePath; 28 this.ExportBasePath = exportBasePath;
26 this.IntermediateFolder = intermediateFolder; 29 this.IntermediateFolder = intermediateFolder;
@@ -32,6 +35,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
32 35
33 private IBackendHelper BackendHelper { get; } 36 private IBackendHelper BackendHelper { get; }
34 37
38 private FileSystemManager FileSystemManager { get; }
39
35 private string TransformFile { get; } 40 private string TransformFile { get; }
36 41
37 private string ExportBasePath { get; } 42 private string ExportBasePath { get; }
@@ -90,7 +95,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
90 msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); 95 msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform);
91 96
92 // unbind the database 97 // unbind the database
93 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); 98 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: true);
94 var transformViewOutput = unbindCommand.Execute(); 99 var transformViewOutput = unbindCommand.Execute();
95 100
96 // index the added and possibly modified rows (added rows may also appears as modified rows) 101 // index the added and possibly modified rows (added rows may also appears as modified rows)
@@ -160,7 +165,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
160 } 165 }
161 166
162 // unbind the database 167 // unbind the database
163 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); 168 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: true);
164 var output = unbindCommand.Execute(); 169 var output = unbindCommand.Execute();
165 170
166 // index all the rows to easily find modified rows 171 // index all the rows to easily find modified rows
@@ -302,8 +307,9 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
302 307
303 private void GenerateDatabase(WindowsInstallerData output, string databaseFile) 308 private void GenerateDatabase(WindowsInstallerData output, string databaseFile)
304 { 309 {
305 var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); 310 var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false);
306 command.Execute(); 311 command.Execute();
307 } 312 }
308 } 313 }
309} 314}
315#endif
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs
index b35e3587..c78ea93a 100644
--- a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs
+++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerDecompiler.cs
@@ -3,11 +3,9 @@
3namespace WixToolset.Core.WindowsInstaller 3namespace WixToolset.Core.WindowsInstaller
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.ComponentModel.Design;
7 using System.ComponentModel;
8 using System.IO; 7 using System.IO;
9 using System.Linq; 8 using System.Linq;
10 using WixToolset.Core.Native.Msi;
11 using WixToolset.Core.WindowsInstaller.Decompile; 9 using WixToolset.Core.WindowsInstaller.Decompile;
12 using WixToolset.Core.WindowsInstaller.Unbind; 10 using WixToolset.Core.WindowsInstaller.Unbind;
13 using WixToolset.Data; 11 using WixToolset.Data;
@@ -63,56 +61,32 @@ namespace WixToolset.Core.WindowsInstaller
63 61
64 private IWindowsInstallerDecompileResult Execute(IWindowsInstallerDecompileContext context) 62 private IWindowsInstallerDecompileResult Execute(IWindowsInstallerDecompileContext context)
65 { 63 {
66 var result = context.ServiceProvider.GetService<IWindowsInstallerDecompileResult>(); 64 // Delete the directory and its files to prevent cab extraction failure due to an existing file.
67 65 if (Directory.Exists(context.ExtractFolder))
68 try
69 { 66 {
70 using (var database = new Database(context.DecompilePath, OpenDatabase.ReadOnly)) 67 Directory.Delete(context.ExtractFolder, true);
71 {
72 // Delete the directory and its files to prevent cab extraction failure due to an existing file.
73 if (Directory.Exists(context.ExtractFolder))
74 {
75 Directory.Delete(context.ExtractFolder, true);
76 }
77
78 var backendHelper = context.ServiceProvider.GetService<IWindowsInstallerBackendHelper>();
79 var decompilerHelper = context.ServiceProvider.GetService<IWindowsInstallerDecompilerHelper>();
80
81 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, database, context.DecompilePath, context.DecompileType, context.ExtractFolder, context.IntermediateFolder, context.IsAdminImage, suppressDemodularization: false, skipSummaryInfo: false);
82 var output = unbindCommand.Execute();
83 var extractedFilePaths = new List<string>(unbindCommand.ExportedFiles);
84
85 var decompiler = new Decompiler(this.Messaging, backendHelper, decompilerHelper, context.Extensions, context.ExtensionData, context.SymbolDefinitionCreator, context.BaseSourcePath, context.SuppressCustomTables, context.SuppressDroppingEmptyTables, context.SuppressRelativeActionSequencing, context.SuppressUI, context.TreatProductAsModule);
86 result.Document = decompiler.Decompile(output);
87
88 result.Platform = GetPlatformFromOutput(output);
89
90 // extract the files from the cabinets
91 if (!String.IsNullOrEmpty(context.ExtractFolder) && !context.SuppressExtractCabinets)
92 {
93 var fileDirectory = String.IsNullOrEmpty(context.CabinetExtractFolder) ? Path.Combine(context.ExtractFolder, "File") : context.CabinetExtractFolder;
94
95 var extractCommand = new ExtractCabinetsCommand(output, database, context.DecompilePath, fileDirectory, context.IntermediateFolder, context.TreatProductAsModule);
96 extractCommand.Execute();
97
98 extractedFilePaths.AddRange(extractCommand.ExtractedFiles);
99 result.ExtractedFilePaths = extractedFilePaths;
100 }
101 else
102 {
103 result.ExtractedFilePaths = new string[0];
104 }
105 }
106 } 68 }
107 catch (Win32Exception e)
108 {
109 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
110 {
111 throw new WixException(ErrorMessages.OpenDatabaseFailed(context.DecompilePath));
112 }
113 69
114 throw; 70 var backendHelper = context.ServiceProvider.GetService<IWindowsInstallerBackendHelper>();
115 } 71
72 var pathResolver = context.ServiceProvider.GetService<IPathResolver>();
73
74 var extractFilesFolder = context.SuppressExtractCabinets || (String.IsNullOrEmpty(context.CabinetExtractFolder) && String.IsNullOrEmpty(context.ExtractFolder)) ? null :
75 String.IsNullOrEmpty(context.CabinetExtractFolder) ? Path.Combine(context.ExtractFolder, "File") : context.CabinetExtractFolder;
76
77 var outputType = context.TreatProductAsModule ? OutputType.Module : context.DecompileType;
78 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, pathResolver, context.DecompilePath, outputType, context.ExtractFolder, extractFilesFolder, context.IntermediateFolder, enableDemodularization: true, skipSummaryInfo: false);
79 var output = unbindCommand.Execute();
80 var extractedFilePaths = unbindCommand.ExportedFiles;
81
82 var decompilerHelper = context.ServiceProvider.GetService<IWindowsInstallerDecompilerHelper>();
83 var decompiler = new Decompiler(this.Messaging, backendHelper, decompilerHelper, context.Extensions, context.ExtensionData, context.SymbolDefinitionCreator, context.BaseSourcePath, context.SuppressCustomTables, context.SuppressDroppingEmptyTables, context.SuppressRelativeActionSequencing, context.SuppressUI, context.TreatProductAsModule);
84 var document = decompiler.Decompile(output);
85
86 var result = context.ServiceProvider.GetService<IWindowsInstallerDecompileResult>();
87 result.Document = document;
88 result.Platform = GetPlatformFromOutput(output);
89 result.ExtractedFilePaths = extractedFilePaths.ToList();
116 90
117 return result; 91 return result;
118 } 92 }
diff --git a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs
index a0798e62..025affd3 100644
--- a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs
+++ b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs
@@ -4,7 +4,6 @@ namespace WixToolset.Core.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO; 7 using System.IO;
9 using System.Linq; 8 using System.Linq;
10 using System.Security.Cryptography; 9 using System.Security.Cryptography;
@@ -41,9 +40,9 @@ namespace WixToolset.Core.Bind
41 { 40 {
42 var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); 41 var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath);
43 var unique = this.HashUri(uri.AbsoluteUri); 42 var unique = this.HashUri(uri.AbsoluteUri);
44 var extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileId); 43 var extractedName = String.Concat(localFileNameWithoutExtension, "_", unique);
45 44
46 extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName)); 45 extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName, embeddedFileId));
47 extracts.Add(embeddedFileId, extractPath); 46 extracts.Add(embeddedFileId, extractPath);
48 } 47 }
49 48
diff --git a/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs
index 794208e5..8a5299d9 100644
--- a/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs
+++ b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs
@@ -16,186 +16,121 @@ namespace WixToolset.Core.Bind
16 /// </summary> 16 /// </summary>
17 internal class ResolveFieldsCommand 17 internal class ResolveFieldsCommand
18 { 18 {
19 public IMessaging Messaging { private get; set; } 19 public ResolveFieldsCommand(IMessaging messaging, IFileResolver fileResolver, IVariableResolver variableResolver, IReadOnlyCollection<IBindPath> bindPaths, IReadOnlyCollection<IResolverExtension> extensions, ExtractEmbeddedFiles filesWithEmbeddedFiles, string intermediateFolder, Intermediate intermediate, bool allowUnresolvedVariables)
20 {
21 this.Messaging = messaging;
22 this.FileResolver = fileResolver;
23 this.VariableResolver = variableResolver;
24 this.BindPaths = bindPaths;
25 this.Extensions = extensions;
26 this.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
27 this.IntermediateFolder = intermediateFolder;
28 this.Intermediate = intermediate;
29 this.AllowUnresolvedVariables = allowUnresolvedVariables;
30 }
20 31
21 public bool BuildingPatch { private get; set; } 32 private IMessaging Messaging { get; }
22 33
23 public IVariableResolver VariableResolver { private get; set; } 34 private IFileResolver FileResolver { get; }
24 35
25 public IEnumerable<IBindPath> BindPaths { private get; set; } 36 private IVariableResolver VariableResolver { get; }
26 37
27 public IEnumerable<IResolverExtension> Extensions { private get; set; } 38 private IEnumerable<IBindPath> BindPaths { get; }
28 39
29 public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } 40 private IEnumerable<IResolverExtension> Extensions { get; }
30 41
31 public string IntermediateFolder { private get; set; } 42 private ExtractEmbeddedFiles FilesWithEmbeddedFiles { get; }
32 43
33 public Intermediate Intermediate { private get; set; } 44 private string IntermediateFolder { get; }
34 45
35 public bool SupportDelayedResolution { private get; set; } 46 private Intermediate Intermediate { get; }
36 47
37 public bool AllowUnresolvedVariables { private get; set; } 48 private bool AllowUnresolvedVariables { get; }
38 49
39 public IReadOnlyCollection<DelayedField> DelayedFields { get; private set; } 50 public IReadOnlyCollection<DelayedField> DelayedFields { get; private set; }
40 51
41 public void Execute() 52 public void Execute()
42 { 53 {
43 var delayedFields = this.SupportDelayedResolution ? new List<DelayedField>() : null; 54 var delayedFields = new List<DelayedField>();
44 55
45 var fileResolver = new FileResolver(this.BindPaths, this.Extensions); 56 var bindPaths = this.BindPaths.Where(b => b.Stage == BindStage.Normal).ToList();
46 57
47 // Build the column lookup only when needed. 58 // Build the column lookup only when needed.
48 Dictionary<string, WixCustomTableColumnSymbol> customColumnsById = null; 59 Dictionary<string, WixCustomTableColumnSymbol> customColumnsById = null;
49 60
50 foreach (var sections in this.Intermediate.Sections) 61 foreach (var symbol in this.Intermediate.Sections.SelectMany(s => s.Symbols))
51 { 62 {
52 foreach (var symbol in sections.Symbols) 63 foreach (var field in symbol.Fields.Where(f => !f.IsNull()))
53 { 64 {
54 foreach (var field in symbol.Fields) 65 var fieldType = field.Type;
66
67 // Custom table cells require an extra look up to the column definition as the
68 // cell's data type is always a string (because strings can store anything) but
69 // the column definition may be more specific.
70 if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell)
55 { 71 {
56 if (field.IsNull()) 72 // We only care about the Data in a CustomTable cell.
73 if (field.Name != nameof(WixCustomTableCellSymbolFields.Data))
57 { 74 {
58 continue; 75 continue;
59 } 76 }
60 77
61 var fieldType = field.Type; 78 if (customColumnsById == null)
62
63 // Custom table cells require an extra look up to the column definition as the
64 // cell's data type is always a string (because strings can store anything) but
65 // the column definition may be more specific.
66 if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell)
67 { 79 {
68 // We only care about the Data in a CustomTable cell. 80 customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType<WixCustomTableColumnSymbol>()).ToDictionary(t => t.Id.Id);
69 if (field.Name != nameof(WixCustomTableCellSymbolFields.Data))
70 {
71 continue;
72 }
73
74 if (customColumnsById == null)
75 {
76 customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType<WixCustomTableColumnSymbol>()).ToDictionary(t => t.Id.Id);
77 }
78
79 if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn))
80 {
81 fieldType = customColumn.Type;
82 }
83 } 81 }
84 82
85 // Check to make sure we're in a scenario where we can handle variable resolution. 83 if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn))
86 if (null != delayedFields)
87 { 84 {
88 // resolve localization and wix variables 85 fieldType = customColumn.Type;
89 if (fieldType == IntermediateFieldType.String)
90 {
91 var original = field.AsString();
92 if (!String.IsNullOrEmpty(original))
93 {
94 var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables);
95 if (resolution.UpdatedValue)
96 {
97 field.Set(resolution.Value);
98 }
99
100 if (resolution.DelayedResolve)
101 {
102 delayedFields.Add(new DelayedField(symbol, field));
103 }
104 }
105 }
106 }
107
108 // Move to next symbol if we've hit an error resolving variables.
109 if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field.
110 {
111 continue;
112 } 86 }
87 }
113 88
114 // Resolve file paths 89 // Check to make sure we're in a scenario where we can handle variable resolution.
115 if (fieldType == IntermediateFieldType.Path) 90 if (null != delayedFields)
91 {
92 // resolve localization and wix variables
93 if (fieldType == IntermediateFieldType.String)
116 { 94 {
117 this.ResolvePathField(fileResolver, symbol, field); 95 var original = field.AsString();
118 96 if (!String.IsNullOrEmpty(original))
119#if TODO_PATCHING
120 if (null != objectField.PreviousData)
121 { 97 {
122 objectField.PreviousData = this.BindVariableResolver.ResolveVariables(symbol.SourceLineNumbers, objectField.PreviousData, false, out isDefault); 98 var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables);
123 99 if (resolution.UpdatedValue)
124 if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field.
125 { 100 {
126 // file is compressed in a cabinet (and not modified above) 101 field.Set(resolution.Value);
127 if (objectField.PreviousEmbeddedFileIndex.HasValue && isDefault) 102 }
128 {
129 // when loading transforms from disk, PreviousBaseUri may not have been set
130 if (null == objectField.PreviousBaseUri)
131 {
132 objectField.PreviousBaseUri = objectField.BaseUri;
133 }
134
135 string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder);
136
137 // set the path to the file once its extracted from the cabinet
138 objectField.PreviousData = extractPath;
139 }
140 else if (null != objectField.PreviousData) // non-compressed file (or localized value)
141 {
142 try
143 {
144 if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated)
145 {
146 // resolve the path to the file
147 objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Normal);
148 }
149 else
150 {
151 if (fileResolver.RebaseTarget)
152 {
153 // if -bt is used, it come here
154 // Try to use the original unresolved source from either target build or update build
155 // If both target and updated are of old wixpdb, it behaves the same as today, no re-base logic here
156 // If target is old version and updated is new version, it uses unresolved path from updated build
157 // If both target and updated are of new versions, it uses unresolved path from target build
158 if (null != objectField.UnresolvedPreviousData || null != objectField.UnresolvedData)
159 {
160 objectField.PreviousData = objectField.UnresolvedPreviousData ?? objectField.UnresolvedData;
161 }
162 }
163
164 // resolve the path to the file
165 objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Target);
166 103
167 } 104 if (resolution.DelayedResolve)
168 } 105 {
169 catch (WixFileNotFoundException) 106 delayedFields.Add(new DelayedField(symbol, field));
170 {
171 // display the error with source line information
172 Messaging.Instance.Write(WixErrors.FileNotFound(symbol.SourceLineNumbers, (string)objectField.PreviousData));
173 }
174 }
175 } 107 }
176 } 108 }
177#endif
178 } 109 }
179 } 110 }
111
112 // Move to next symbol if we've hit an error resolving variables.
113 if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field.
114 {
115 continue;
116 }
117
118 // Resolve file paths
119 if (fieldType == IntermediateFieldType.Path)
120 {
121 this.ResolvePathField(this.FileResolver, bindPaths, symbol, field);
122 }
180 } 123 }
181 } 124 }
182 125
183 this.DelayedFields = delayedFields; 126 this.DelayedFields = delayedFields;
184 } 127 }
185 128
186 private void ResolvePathField(FileResolver fileResolver, IntermediateSymbol symbol, IntermediateField field) 129 private void ResolvePathField(IFileResolver fileResolver, IEnumerable<IBindPath> bindPaths, IntermediateSymbol symbol, IntermediateField field)
187 { 130 {
188 var fieldValue = field.AsPath(); 131 var fieldValue = field.AsPath();
189 var originalFieldPath = fieldValue.Path; 132 var originalFieldPath = fieldValue.Path;
190 133
191#if TODO_PATCHING
192 // Skip file resolution if the file is to be deleted.
193 if (RowOperation.Delete == symbol.Operation)
194 {
195 continue;
196 }
197#endif
198
199 // If the file is embedded and if the previous value has a bind variable in the path 134 // If the file is embedded and if the previous value has a bind variable in the path
200 // which gets modified by resolving the previous value again then switch to that newly 135 // which gets modified by resolving the previous value again then switch to that newly
201 // resolved path instead of using the embedded file. 136 // resolved path instead of using the embedded file.
@@ -221,45 +156,7 @@ namespace WixToolset.Core.Bind
221 { 156 {
222 try 157 try
223 { 158 {
224 var resolvedPath = fieldValue.Path; 159 var resolvedPath = fileResolver.ResolveFile(fieldValue.Path, this.Extensions, bindPaths, BindStage.Normal, symbol.SourceLineNumbers, symbol.Definition);
225
226 if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe)
227 {
228#if TODO_PATCHING
229 // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file
230 if (null == objectField.UnresolvedData)
231 {
232 objectField.UnresolvedData = (string)objectField.Data;
233 }
234#endif
235 resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal);
236 }
237 else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic)
238 {
239 resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal);
240 }
241#if TODO_PATCHING
242 else // Re-base binding path scenario caused by pyro.exe -bt -bu
243 {
244 // by default, use the resolved Data for file lookup
245 string filePathToResolve = (string)objectField.Data;
246
247 // if -bu is used in pyro command, this condition holds true and the tool
248 // will use pre-resolved source for new wixpdb file
249 if (fileResolver.RebaseUpdated)
250 {
251 // try to use the unResolved Source if it exists.
252 // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll
253 // Old version of winpdb file does not contain this attribute and the value is null.
254 if (null != objectField.UnresolvedData)
255 {
256 filePathToResolve = objectField.UnresolvedData;
257 }
258 }
259
260 objectField.Data = fileResolver.ResolveFile(filePathToResolve, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Updated);
261 }
262#endif
263 160
264 if (!String.Equals(originalFieldPath, resolvedPath, StringComparison.OrdinalIgnoreCase)) 161 if (!String.Equals(originalFieldPath, resolvedPath, StringComparison.OrdinalIgnoreCase))
265 { 162 {
diff --git a/src/wix/WixToolset.Core/BindContext.cs b/src/wix/WixToolset.Core/BindContext.cs
index 9dd6aa46..1c1f7528 100644
--- a/src/wix/WixToolset.Core/BindContext.cs
+++ b/src/wix/WixToolset.Core/BindContext.cs
@@ -18,7 +18,7 @@ namespace WixToolset.Core
18 18
19 public IServiceProvider ServiceProvider { get; } 19 public IServiceProvider ServiceProvider { get; }
20 20
21 public IReadOnlyCollection<BindPath> BindPaths { get; set; } 21 public IReadOnlyCollection<IBindPath> BindPaths { get; set; }
22 22
23 public string BurnStubPath { get; set; } 23 public string BurnStubPath { get; set; }
24 24
diff --git a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
index dc4e0a6d..34520fc0 100644
--- a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
+++ b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -48,7 +48,9 @@ namespace WixToolset.Core.CommandLine
48 { 48 {
49 new CommandLineHelpSwitch("-arch", "Set the architecture of the output."), 49 new CommandLineHelpSwitch("-arch", "Set the architecture of the output."),
50 new CommandLineHelpSwitch("-bindfiles", "-bf", "Bind files into an output .wixlib. Ignored if not building a .wixlib."), 50 new CommandLineHelpSwitch("-bindfiles", "-bf", "Bind files into an output .wixlib. Ignored if not building a .wixlib."),
51 new CommandLineHelpSwitch("-bindpath", "Bind path to search for content files."), 51 new CommandLineHelpSwitch("-bindpath", "-b", "Bind path to search for content files."),
52 new CommandLineHelpSwitch("-bindpath:target", "-bt", "Bind path to search for target package's content files. Only used when building a patch."),
53 new CommandLineHelpSwitch("-bindpath:update", "-bu", "Bind path to search for update package's content files. Only used when building a patch."),
52 new CommandLineHelpSwitch("-cabcache", "-cc", "Set a folder to cache cabinets across builds."), 54 new CommandLineHelpSwitch("-cabcache", "-cc", "Set a folder to cache cabinets across builds."),
53 new CommandLineHelpSwitch("-culture", "Adds a culture to filter localization files."), 55 new CommandLineHelpSwitch("-culture", "Adds a culture to filter localization files."),
54 new CommandLineHelpSwitch("-define", "-d", "Sets a preprocessor variable."), 56 new CommandLineHelpSwitch("-define", "-d", "Sets a preprocessor variable."),
@@ -292,6 +294,7 @@ namespace WixToolset.Core.CommandLine
292 { 294 {
293 { 295 {
294 var context = this.ServiceProvider.GetService<IBindContext>(); 296 var context = this.ServiceProvider.GetService<IBindContext>();
297 context.BindPaths = bindPaths;
295 //context.CabbingThreadCount = this.CabbingThreadCount; 298 //context.CabbingThreadCount = this.CabbingThreadCount;
296 context.CabCachePath = cabCachePath; 299 context.CabCachePath = cabCachePath;
297 context.ResolvedCodepage = resolveResult.Codepage; 300 context.ResolvedCodepage = resolveResult.Codepage;
@@ -534,11 +537,25 @@ namespace WixToolset.Core.CommandLine
534 this.BindFiles = true; 537 this.BindFiles = true;
535 return true; 538 return true;
536 539
540 case "b":
537 case "bindpath": 541 case "bindpath":
542 case "bt":
543 case "bindpath:target":
544 case "bu":
545 case "bindpath:update":
538 { 546 {
539 var value = parser.GetNextArgumentOrError(arg); 547 var value = parser.GetNextArgumentOrError(arg);
540 if (value != null && this.TryParseBindPath(value, out var bindPath)) 548 if (value != null && this.TryParseBindPath(arg, value, out var bindPath))
541 { 549 {
550 if (parameter == "bt" || parameter.EndsWith("target"))
551 {
552 bindPath.Stage = BindStage.Target;
553 }
554 else if (parameter == "bu" || parameter.EndsWith("update"))
555 {
556 bindPath.Stage = BindStage.Updated;
557 }
558
542 this.BindPaths.Add(bindPath); 559 this.BindPaths.Add(bindPath);
543 } 560 }
544 return true; 561 return true;
@@ -830,7 +847,7 @@ namespace WixToolset.Core.CommandLine
830 return new InputsAndOutputs(codePaths, localizationPaths, libraryPaths, wixipls, outputPath, outputType, pdbPath, this.PdbType); 847 return new InputsAndOutputs(codePaths, localizationPaths, libraryPaths, wixipls, outputPath, outputType, pdbPath, this.PdbType);
831 } 848 }
832 849
833 private bool TryParseBindPath(string bindPath, out IBindPath bp) 850 private bool TryParseBindPath(string argument, string bindPath, out IBindPath bp)
834 { 851 {
835 var namedPath = bindPath.Split(BindPathSplit, 2); 852 var namedPath = bindPath.Split(BindPathSplit, 2);
836 853
@@ -848,7 +865,7 @@ namespace WixToolset.Core.CommandLine
848 865
849 if (File.Exists(bp.Path)) 866 if (File.Exists(bp.Path))
850 { 867 {
851 this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); 868 this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(argument, bp.Path));
852 return false; 869 return false;
853 } 870 }
854 871
diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs
index d04ae491..8f1ae7fb 100644
--- a/src/wix/WixToolset.Core/Compiler.cs
+++ b/src/wix/WixToolset.Core/Compiler.cs
@@ -7706,7 +7706,6 @@ namespace WixToolset.Core
7706 var validationFlags = TransformFlags.PatchTransformDefault; 7706 var validationFlags = TransformFlags.PatchTransformDefault;
7707 string baselineFile = null; 7707 string baselineFile = null;
7708 string updateFile = null; 7708 string updateFile = null;
7709 string transformFile = null;
7710 7709
7711 foreach (var attrib in node.Attributes()) 7710 foreach (var attrib in node.Attributes())
7712 { 7711 {
@@ -7726,9 +7725,6 @@ namespace WixToolset.Core
7726 case "UpdateFile": 7725 case "UpdateFile":
7727 updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); 7726 updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7728 break; 7727 break;
7729 case "TransformFile":
7730 transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7731 break;
7732 default: 7728 default:
7733 this.Core.UnexpectedAttribute(node, attrib); 7729 this.Core.UnexpectedAttribute(node, attrib);
7734 break; 7730 break;
@@ -7746,26 +7742,14 @@ namespace WixToolset.Core
7746 id = Identifier.Invalid; 7742 id = Identifier.Invalid;
7747 } 7743 }
7748 7744
7749 if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile)) 7745 if (String.IsNullOrEmpty(baselineFile))
7750 { 7746 {
7751 if (String.IsNullOrEmpty(baselineFile)) 7747 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile"));
7752 {
7753 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile"));
7754 }
7755
7756 if (String.IsNullOrEmpty(updateFile))
7757 {
7758 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile"));
7759 }
7760
7761 if (!String.IsNullOrEmpty(transformFile))
7762 {
7763 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile"));
7764 }
7765 } 7748 }
7766 else if (String.IsNullOrEmpty(transformFile)) 7749
7750 if (String.IsNullOrEmpty(updateFile))
7767 { 7751 {
7768 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true)); 7752 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile"));
7769 } 7753 }
7770 7754
7771 foreach (var child in node.Elements()) 7755 foreach (var child in node.Elements())
@@ -7805,7 +7789,6 @@ namespace WixToolset.Core
7805 ValidationFlags = validationFlags, 7789 ValidationFlags = validationFlags,
7806 BaselineFile = new IntermediateFieldPathValue { Path = baselineFile }, 7790 BaselineFile = new IntermediateFieldPathValue { Path = baselineFile },
7807 UpdateFile = new IntermediateFieldPathValue { Path = updateFile }, 7791 UpdateFile = new IntermediateFieldPathValue { Path = updateFile },
7808 TransformFile = new IntermediateFieldPathValue { Path = transformFile },
7809 }); 7792 });
7810 } 7793 }
7811 } 7794 }
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs
index 20a47637..4a5cc607 100644
--- a/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs
+++ b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs
@@ -6,8 +6,6 @@ namespace WixToolset.Core.ExtensibilityServices
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using WixToolset.Core.Bind; 7 using WixToolset.Core.Bind;
8 using WixToolset.Data; 8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Data.WindowsInstaller.Rows;
11 using WixToolset.Extensibility.Data; 9 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services; 10 using WixToolset.Extensibility.Services;
13 11
@@ -17,21 +15,6 @@ namespace WixToolset.Core.ExtensibilityServices
17 { 15 {
18 } 16 }
19 17
20 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly)
21 {
22 return new FileFacade(file, assembly);
23 }
24
25 public IFileFacade CreateFileFacade(FileRow fileRow)
26 {
27 return new FileFacade(fileRow);
28 }
29
30 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol)
31 {
32 return new FileFacade(true, fileSymbol);
33 }
34
35 public string CreateGuid() 18 public string CreateGuid()
36 { 19 {
37 return Common.GenerateGuid(); 20 return Common.GenerateGuid();
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs
deleted file mode 100644
index 65043658..00000000
--- a/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs
+++ /dev/null
@@ -1,175 +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.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9 using WixToolset.Data.WindowsInstaller;
10 using WixToolset.Data.WindowsInstaller.Rows;
11 using WixToolset.Extensibility.Data;
12
13 internal class FileFacade : IFileFacade
14 {
15 public FileFacade(FileSymbol file, AssemblySymbol assembly)
16 {
17 this.FileSymbol = file;
18 this.AssemblySymbol = assembly;
19
20 this.Identifier = file.Id;
21 this.ComponentRef = file.ComponentRef;
22 }
23
24 public FileFacade(bool fromModule, FileSymbol file)
25 {
26 this.FromModule = fromModule;
27 this.FileSymbol = file;
28
29 this.Identifier = file.Id;
30 this.ComponentRef = file.ComponentRef;
31 }
32
33 public FileFacade(FileRow row)
34 {
35 this.FromTransform = true;
36 this.FileRow = row;
37
38 this.Identifier = new Identifier(AccessModifier.Section, row.File);
39 this.ComponentRef = row.Component;
40 }
41
42 public bool FromModule { get; }
43
44 public bool FromTransform { get; }
45
46 private FileRow FileRow { get; }
47
48 private FileSymbol FileSymbol { get; }
49
50 private AssemblySymbol AssemblySymbol { get; }
51
52 public string Id => this.Identifier.Id;
53
54 public Identifier Identifier { get; }
55
56 public string ComponentRef { get; }
57
58 public int DiskId
59 {
60 get => this.FileRow == null ? this.FileSymbol.DiskId ?? 1 : this.FileRow.DiskId;
61 set
62 {
63 if (this.FileRow == null)
64 {
65 this.FileSymbol.DiskId = value;
66 }
67 else
68 {
69 this.FileRow.DiskId = value;
70 }
71 }
72 }
73
74 public string FileName => this.FileRow == null ? this.FileSymbol.Name : this.FileRow.FileName;
75
76 public int FileSize
77 {
78 get => this.FileRow == null ? this.FileSymbol.FileSize : this.FileRow.FileSize;
79 set
80 {
81 if (this.FileRow == null)
82 {
83 this.FileSymbol.FileSize = value;
84 }
85 else
86 {
87 this.FileRow.FileSize = value;
88 }
89 }
90 }
91
92 public string Language
93 {
94 get => this.FileRow == null ? this.FileSymbol.Language : this.FileRow.Language;
95 set
96 {
97 if (this.FileRow == null)
98 {
99 this.FileSymbol.Language = value;
100 }
101 else
102 {
103 this.FileRow.Language = value;
104 }
105 }
106 }
107
108 public int? PatchGroup => this.FileRow == null ? this.FileSymbol.PatchGroup : null;
109
110 public int Sequence
111 {
112 get => this.FileRow == null ? this.FileSymbol.Sequence : this.FileRow.Sequence;
113 set
114 {
115 if (this.FileRow == null)
116 {
117 this.FileSymbol.Sequence = value;
118 }
119 else
120 {
121 this.FileRow.Sequence = value;
122 }
123 }
124 }
125
126 public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers;
127
128 public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source;
129
130 public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed;
131
132 public bool Uncompressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
133
134 public string Version
135 {
136 get => this.FileRow == null ? this.FileSymbol.Version : this.FileRow.Version;
137 set
138 {
139 if (this.FileRow == null)
140 {
141 this.FileSymbol.Version = value;
142 }
143 else
144 {
145 this.FileRow.Version = value;
146 }
147 }
148 }
149
150 public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblySymbol?.Type : null;
151
152 public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblySymbol?.ApplicationFileRef : throw new NotImplementedException();
153
154 public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblySymbol?.ManifestFileRef : throw new NotImplementedException();
155
156 /// <summary>
157 /// Gets the set of MsiAssemblyName rows created for this file.
158 /// </summary>
159 /// <value>RowCollection of MsiAssemblyName table.</value>
160 public List<MsiAssemblyNameSymbol> AssemblyNames { get; set; }
161
162 /// <summary>
163 /// Gets or sets the MsiFileHash row for this file.
164 /// </summary>
165 public MsiFileHashSymbol Hash { get; set; }
166
167 /// <summary>
168 /// Allows direct access to the underlying FileRow as requried for patching.
169 /// </summary>
170 public FileRow GetFileRow()
171 {
172 return this.FileRow ?? throw new NotImplementedException();
173 }
174 }
175}
diff --git a/src/wix/WixToolset.Core/Bind/FileResolver.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileResolver.cs
index eb878239..8f08e75e 100644
--- a/src/wix/WixToolset.Core/Bind/FileResolver.cs
+++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileResolver.cs
@@ -1,51 +1,24 @@
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.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 System.Linq;
9 using WixToolset.Data; 8 using WixToolset.Data;
10 using WixToolset.Extensibility; 9 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data; 10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12 12
13 internal class FileResolver 13 internal class FileResolver : IFileResolver
14 { 14 {
15 private const string BindPathOpenString = "!(bindpath."; 15 private const string BindPathOpenString = "!(bindpath.";
16 16
17 private FileResolver(IEnumerable<IBindPath> bindPaths) 17 public string ResolveFile(string source, IEnumerable<ILibrarianExtension> librarianExtensions, IEnumerable<IBindPath> bindPaths, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition)
18 {
19 this.BindPaths = (bindPaths ?? Array.Empty<IBindPath>()).ToLookup(b => b.Stage);
20 this.RebaseTarget = this.BindPaths[BindStage.Target].Any();
21 this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any();
22 }
23
24 public FileResolver(IEnumerable<IBindPath> bindPaths, IEnumerable<IResolverExtension> extensions) : this(bindPaths)
25 {
26 this.ResolverExtensions = extensions ?? Array.Empty<IResolverExtension>();
27 }
28
29 public FileResolver(IEnumerable<IBindPath> bindPaths, IEnumerable<ILibrarianExtension> extensions) : this(bindPaths)
30 {
31 this.LibrarianExtensions = extensions ?? Array.Empty<ILibrarianExtension>();
32 }
33
34 private ILookup<BindStage, IBindPath> BindPaths { get; }
35
36 public bool RebaseTarget { get; }
37
38 public bool RebaseUpdated { get; }
39
40 private IEnumerable<IResolverExtension> ResolverExtensions { get; }
41
42 private IEnumerable<ILibrarianExtension> LibrarianExtensions { get; }
43
44 public string Resolve(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string source)
45 { 18 {
46 var checkedPaths = new List<string>(); 19 var checkedPaths = new List<string>();
47 20
48 foreach (var extension in this.LibrarianExtensions) 21 foreach (var extension in librarianExtensions)
49 { 22 {
50 var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source); 23 var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source);
51 24
@@ -60,19 +33,10 @@ namespace WixToolset.Core.Bind
60 } 33 }
61 } 34 }
62 35
63 return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, BindStage.Normal, checkedPaths); 36 return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindPaths, checkedPaths);
64 } 37 }
65 38
66 /// <summary> 39 public string ResolveFile(string source, IEnumerable<IResolverExtension> resolverExtensions, IEnumerable<IBindPath> bindPaths, BindStage bindStage, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, IEnumerable<string> alreadyCheckedPaths = null)
67 /// Resolves the source path of a file using binder extensions.
68 /// </summary>
69 /// <param name="source">Original source value.</param>
70 /// <param name="symbolDefinition">Optional type 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>
73 /// <param name="alreadyCheckedPaths">Optional collection of paths already checked.</param>
74 /// <returns>Should return a valid path for the stream to be imported.</returns>
75 public string ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable<string> alreadyCheckedPaths = null)
76 { 40 {
77 var checkedPaths = new List<string>(); 41 var checkedPaths = new List<string>();
78 42
@@ -81,7 +45,7 @@ namespace WixToolset.Core.Bind
81 checkedPaths.AddRange(alreadyCheckedPaths); 45 checkedPaths.AddRange(alreadyCheckedPaths);
82 } 46 }
83 47
84 foreach (var extension in this.ResolverExtensions) 48 foreach (var extension in resolverExtensions)
85 { 49 {
86 var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage); 50 var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage);
87 51
@@ -96,10 +60,10 @@ namespace WixToolset.Core.Bind
96 } 60 }
97 } 61 }
98 62
99 return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindStage, checkedPaths); 63 return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindPaths, checkedPaths);
100 } 64 }
101 65
102 private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, List<string> checkedPaths) 66 private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, IEnumerable<IBindPath> bindPaths, List<string> checkedPaths)
103 { 67 {
104 string resolved = null; 68 string resolved = null;
105 69
@@ -135,8 +99,6 @@ namespace WixToolset.Core.Bind
135 pathWithoutSourceDir = path.Substring(10); 99 pathWithoutSourceDir = path.Substring(10);
136 } 100 }
137 101
138 var bindPaths = this.BindPaths[bindStage];
139
140 foreach (var bindPath in bindPaths) 102 foreach (var bindPath in bindPaths)
141 { 103 {
142 if (String.IsNullOrEmpty(bindName)) 104 if (String.IsNullOrEmpty(bindName))
@@ -145,36 +107,18 @@ namespace WixToolset.Core.Bind
145 { 107 {
146 if (!String.IsNullOrEmpty(pathWithoutSourceDir)) 108 if (!String.IsNullOrEmpty(pathWithoutSourceDir))
147 { 109 {
148 var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); 110 resolved = ResolveWithBindPath(bindPath.Path, pathWithoutSourceDir, checkedPaths);
149
150 checkedPaths.Add(filePath);
151 if (CheckFileExists(filePath))
152 {
153 resolved = filePath;
154 }
155 } 111 }
156 112
157 if (String.IsNullOrEmpty(resolved)) 113 if (String.IsNullOrEmpty(resolved))
158 { 114 {
159 var filePath = Path.Combine(bindPath.Path, path); 115 resolved = ResolveWithBindPath(bindPath.Path, path, checkedPaths);
160
161 checkedPaths.Add(filePath);
162 if (CheckFileExists(filePath))
163 {
164 resolved = filePath;
165 }
166 } 116 }
167 } 117 }
168 } 118 }
169 else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase)) 119 else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase))
170 { 120 {
171 var filePath = Path.Combine(bindPath.Path, path); 121 resolved = ResolveWithBindPath(bindPath.Path, path, checkedPaths);
172
173 checkedPaths.Add(filePath);
174 if (CheckFileExists(filePath))
175 {
176 resolved = filePath;
177 }
178 } 122 }
179 123
180 if (!String.IsNullOrEmpty(resolved)) 124 if (!String.IsNullOrEmpty(resolved))
@@ -186,12 +130,26 @@ namespace WixToolset.Core.Bind
186 130
187 if (null == resolved) 131 if (null == resolved)
188 { 132 {
189 throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition.Name, checkedPaths)); 133 throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition?.Name, checkedPaths));
190 } 134 }
191 135
192 return resolved; 136 return resolved;
193 } 137 }
194 138
139 private static string ResolveWithBindPath(string bindPath, string relativePath, List<string> checkedPaths)
140 {
141 var filePath = Path.Combine(bindPath, relativePath);
142
143 checkedPaths.Add(filePath);
144
145 if (CheckFileExists(filePath))
146 {
147 return filePath;
148 }
149
150 return null;
151 }
152
195 private static bool CheckFileExists(string path) 153 private static bool CheckFileExists(string path)
196 { 154 {
197 try 155 try
diff --git a/src/wix/WixToolset.Core/Librarian.cs b/src/wix/WixToolset.Core/Librarian.cs
index 2762fb33..968dd946 100644
--- a/src/wix/WixToolset.Core/Librarian.cs
+++ b/src/wix/WixToolset.Core/Librarian.cs
@@ -5,7 +5,6 @@ namespace WixToolset.Core
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Linq; 7 using System.Linq;
8 using WixToolset.Core.Bind;
9 using WixToolset.Core.Link; 8 using WixToolset.Core.Link;
10 using WixToolset.Data; 9 using WixToolset.Data;
11 using WixToolset.Extensibility.Data; 10 using WixToolset.Extensibility.Data;
@@ -21,6 +20,7 @@ namespace WixToolset.Core
21 this.ServiceProvider = serviceProvider; 20 this.ServiceProvider = serviceProvider;
22 21
23 this.Messaging = this.ServiceProvider.GetService<IMessaging>(); 22 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
23 this.FileResolver = this.ServiceProvider.GetService<IFileResolver>();
24 this.LayoutServices = this.ServiceProvider.GetService<ILayoutServices>(); 24 this.LayoutServices = this.ServiceProvider.GetService<ILayoutServices>();
25 } 25 }
26 26
@@ -28,6 +28,8 @@ namespace WixToolset.Core
28 28
29 private IMessaging Messaging { get; } 29 private IMessaging Messaging { get; }
30 30
31 private IFileResolver FileResolver { get; }
32
31 private ILayoutServices LayoutServices { get; } 33 private ILayoutServices LayoutServices { get; }
32 34
33 /// <summary> 35 /// <summary>
@@ -97,7 +99,7 @@ namespace WixToolset.Core
97 { 99 {
98 var variableResolver = this.ServiceProvider.GetService<IVariableResolver>(); 100 var variableResolver = this.ServiceProvider.GetService<IVariableResolver>();
99 101
100 var fileResolver = new FileResolver(context.BindPaths, context.Extensions); 102 var bindPaths = context.BindPaths.Where(b => b.Stage == BindStage.Normal).ToList();
101 103
102 foreach (var symbol in sections.SelectMany(s => s.Symbols)) 104 foreach (var symbol in sections.SelectMany(s => s.Symbols))
103 { 105 {
@@ -109,7 +111,7 @@ namespace WixToolset.Core
109 { 111 {
110 var resolution = variableResolver.ResolveVariables(symbol.SourceLineNumbers, pathField.Path); 112 var resolution = variableResolver.ResolveVariables(symbol.SourceLineNumbers, pathField.Path);
111 113
112 var file = fileResolver.Resolve(symbol.SourceLineNumbers, symbol.Definition, resolution.Value); 114 var file = this.FileResolver.ResolveFile(resolution.Value, context.Extensions, bindPaths, symbol.SourceLineNumbers, symbol.Definition);
113 115
114 if (!String.IsNullOrEmpty(file)) 116 if (!String.IsNullOrEmpty(file))
115 { 117 {
diff --git a/src/wix/WixToolset.Core/Resolver.cs b/src/wix/WixToolset.Core/Resolver.cs
index e93f8e1b..f7aa6ff9 100644
--- a/src/wix/WixToolset.Core/Resolver.cs
+++ b/src/wix/WixToolset.Core/Resolver.cs
@@ -23,12 +23,16 @@ namespace WixToolset.Core
23 this.ServiceProvider = serviceProvider; 23 this.ServiceProvider = serviceProvider;
24 24
25 this.Messaging = serviceProvider.GetService<IMessaging>(); 25 this.Messaging = serviceProvider.GetService<IMessaging>();
26
27 this.FileResolver = serviceProvider.GetService<IFileResolver>();
26 } 28 }
27 29
28 private IServiceProvider ServiceProvider { get; } 30 private IServiceProvider ServiceProvider { get; }
29 31
30 private IMessaging Messaging { get; } 32 private IMessaging Messaging { get; }
31 33
34 private IFileResolver FileResolver { get; }
35
32 public IResolveResult Resolve(IResolveContext context) 36 public IResolveResult Resolve(IResolveContext context)
33 { 37 {
34 foreach (var extension in context.Extensions) 38 foreach (var extension in context.Extensions)
@@ -45,7 +49,7 @@ namespace WixToolset.Core
45 49
46 this.LocalizeUI(variableResolver, context.IntermediateRepresentation); 50 this.LocalizeUI(variableResolver, context.IntermediateRepresentation);
47 51
48 resolveResult = this.DoResolve(context, variableResolver); 52 resolveResult = this.ResolveFields(context, variableResolver);
49 53
50 var primaryLocalization = filteredLocalizations.FirstOrDefault(); 54 var primaryLocalization = filteredLocalizations.FirstOrDefault();
51 55
@@ -71,49 +75,18 @@ namespace WixToolset.Core
71 return resolveResult; 75 return resolveResult;
72 } 76 }
73 77
74 private ResolveResult DoResolve(IResolveContext context, IVariableResolver variableResolver) 78 private ResolveResult ResolveFields(IResolveContext context, IVariableResolver variableResolver)
75 { 79 {
76 var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch);
77
78 var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); 80 var filesWithEmbeddedFiles = new ExtractEmbeddedFiles();
79 81
80 IReadOnlyCollection<DelayedField> delayedFields; 82 IReadOnlyCollection<DelayedField> delayedFields;
81 { 83 {
82 var command = new ResolveFieldsCommand(); 84 var command = new ResolveFieldsCommand(this.Messaging, this.FileResolver, variableResolver, context.BindPaths, context.Extensions, filesWithEmbeddedFiles, context.IntermediateFolder, context.IntermediateRepresentation, context.AllowUnresolvedVariables);
83 command.Messaging = this.Messaging;
84 command.BuildingPatch = buildingPatch;
85 command.VariableResolver = variableResolver;
86 command.BindPaths = context.BindPaths;
87 command.Extensions = context.Extensions;
88 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
89 command.IntermediateFolder = context.IntermediateFolder;
90 command.Intermediate = context.IntermediateRepresentation;
91 command.SupportDelayedResolution = true;
92 command.AllowUnresolvedVariables = context.AllowUnresolvedVariables;
93 command.Execute(); 85 command.Execute();
94 86
95 delayedFields = command.DelayedFields; 87 delayedFields = command.DelayedFields;
96 } 88 }
97 89
98#if TODO_PATCHING
99 if (context.IntermediateRepresentation.SubStorages != null)
100 {
101 foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages)
102 {
103 var command = new ResolveFieldsCommand();
104 command.BuildingPatch = buildingPatch;
105 command.BindVariableResolver = context.WixVariableResolver;
106 command.BindPaths = context.BindPaths;
107 command.Extensions = context.Extensions;
108 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
109 command.IntermediateFolder = context.IntermediateFolder;
110 command.Intermediate = context.IntermediateRepresentation;
111 command.SupportDelayedResolution = false;
112 command.Execute();
113 }
114 }
115#endif
116
117 var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); 90 var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles();
118 91
119 context.IntermediateRepresentation.UpdateLevel(IntermediateLevels.Resolved); 92 context.IntermediateRepresentation.UpdateLevel(IntermediateLevels.Resolved);
diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs
index fa6b369d..5620bcd2 100644
--- a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs
+++ b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs
@@ -27,6 +27,7 @@ namespace WixToolset.Core
27 this.AddService((provider, singletons) => AddSingleton<ILayoutServices>(singletons, new LayoutServices(provider))); 27 this.AddService((provider, singletons) => AddSingleton<ILayoutServices>(singletons, new LayoutServices(provider)));
28 this.AddService((provider, singletons) => AddSingleton<IBackendHelper>(singletons, new BackendHelper(provider))); 28 this.AddService((provider, singletons) => AddSingleton<IBackendHelper>(singletons, new BackendHelper(provider)));
29 this.AddService((provider, singletons) => AddSingleton<IPathResolver>(singletons, new PathResolver())); 29 this.AddService((provider, singletons) => AddSingleton<IPathResolver>(singletons, new PathResolver()));
30 this.AddService((provider, singletons) => AddSingleton<IFileResolver>(singletons, new FileResolver()));
30 this.AddService((provider, singletons) => AddSingleton<IFileSystem>(singletons, new FileSystem())); 31 this.AddService((provider, singletons) => AddSingleton<IFileSystem>(singletons, new FileSystem()));
31 this.AddService((provider, singletons) => AddSingleton<IWixBranding>(singletons, new WixBranding())); 32 this.AddService((provider, singletons) => AddSingleton<IWixBranding>(singletons, new WixBranding()));
32 33
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
index a8aff8d8..c330fd02 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
@@ -5,6 +5,7 @@ namespace WixToolsetTest.CoreIntegration
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.ComponentModel; 7 using System.ComponentModel;
8 using System.Diagnostics;
8 using System.IO; 9 using System.IO;
9 using System.Linq; 10 using System.Linq;
10 using System.Runtime.InteropServices; 11 using System.Runtime.InteropServices;
@@ -22,8 +23,11 @@ namespace WixToolsetTest.CoreIntegration
22 public class PatchFixture : IDisposable 23 public class PatchFixture : IDisposable
23 { 24 {
24 private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd"; 25 private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd";
26 private static readonly XName TargetProductCodeName = PatchNamespace + "TargetProductCode";
27
25 private readonly DisposableFileSystem tempFileSystem; 28 private readonly DisposableFileSystem tempFileSystem;
26 private readonly string tempBaseFolder; 29 private readonly string tempBaseFolder;
30 private readonly string templateSourceFolder;
27 private readonly string templateBaselinePdb; 31 private readonly string templateBaselinePdb;
28 private readonly string templateUpdatePdb; 32 private readonly string templateUpdatePdb;
29 private readonly string templateUpdateNoFilesChangedPdb; 33 private readonly string templateUpdateNoFilesChangedPdb;
@@ -33,14 +37,14 @@ namespace WixToolsetTest.CoreIntegration
33 this.tempFileSystem = new DisposableFileSystem(); 37 this.tempFileSystem = new DisposableFileSystem();
34 this.tempBaseFolder = this.tempFileSystem.GetFolder(); 38 this.tempBaseFolder = this.tempFileSystem.GetFolder();
35 39
36 var templateSourceFolder = TestData.Get(@"TestData", "PatchTemplatePackage"); 40 this.templateSourceFolder = TestData.Get(@"TestData", "PatchTemplatePackage");
37 var tempFolderBaseline = Path.Combine(this.tempBaseFolder, "PatchTemplatePackage", "baseline"); 41 var tempFolderBaseline = Path.Combine(this.tempBaseFolder, "PatchTemplatePackage", "baseline");
38 var tempFolderUpdate = Path.Combine(this.tempBaseFolder, "PatchTemplatePackage", "update"); 42 var tempFolderUpdate = Path.Combine(this.tempBaseFolder, "PatchTemplatePackage", "update");
39 var tempFolderUpdateNoFileChanges = Path.Combine(this.tempBaseFolder, "PatchTemplatePackage", "updatewithoutfilechanges"); 43 var tempFolderUpdateNoFileChanges = Path.Combine(this.tempBaseFolder, "PatchTemplatePackage", "updatewithoutfilechanges");
40 44
41 this.templateBaselinePdb = BuildMsi("Baseline.msi", templateSourceFolder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0", new[] { Path.Combine(templateSourceFolder, ".baseline-data") }); 45 this.templateBaselinePdb = BuildMsi("Baseline.msi", this.templateSourceFolder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0", new[] { Path.Combine(this.templateSourceFolder, ".baseline-data") });
42 this.templateUpdatePdb = BuildMsi("Update.msi", templateSourceFolder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1", new[] { Path.Combine(templateSourceFolder, ".update-data") }); 46 this.templateUpdatePdb = BuildMsi("Update.msi", this.templateSourceFolder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1", new[] { Path.Combine(this.templateSourceFolder, ".update-data") });
43 this.templateUpdateNoFilesChangedPdb = BuildMsi("Update.msi", templateSourceFolder, tempFolderUpdateNoFileChanges, "1.0.1", "1.0.1", "1.0.1", new[] { Path.Combine(templateSourceFolder, ".baseline-data") }); 47 this.templateUpdateNoFilesChangedPdb = BuildMsi("Update.msi", this.templateSourceFolder, tempFolderUpdateNoFileChanges, "1.0.1", "1.0.1", "1.0.1", new[] { Path.Combine(this.templateSourceFolder, ".baseline-data") });
44 } 48 }
45 49
46 public void Dispose() 50 public void Dispose()
@@ -49,7 +53,7 @@ namespace WixToolsetTest.CoreIntegration
49 } 53 }
50 54
51 [Fact] 55 [Fact]
52 public void CanBuildSimplePatch() 56 public void CanBuildSimplePatchUsingWixpdbs()
53 { 57 {
54 var folder = TestData.Get(@"TestData", "PatchSingle"); 58 var folder = TestData.Get(@"TestData", "PatchSingle");
55 59
@@ -66,14 +70,13 @@ namespace WixToolsetTest.CoreIntegration
66 Assert.True(File.Exists(update1Pdb)); 70 Assert.True(File.Exists(update1Pdb));
67 71
68 var doc = GetExtractPatchXml(patchPath); 72 var doc = GetExtractPatchXml(patchPath);
69 WixAssert.StringEqual("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); 73 WixAssert.StringEqual("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(TargetProductCodeName).Value);
70 74
71 var names = Query.GetSubStorageNames(patchPath); 75 var names = Query.GetSubStorageNames(patchPath);
72 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); 76 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names);
73 77
74 var cab = Path.Combine(tempFolder, "foo.cab"); 78 var cab = Path.Combine(tempFolder, "foo.cab");
75 Query.ExtractStream(patchPath, "foo.cab", cab); 79 Query.ExtractStream(patchPath, "foo.cab", cab);
76 Assert.True(File.Exists(cab));
77 80
78 var files = Query.GetCabinetFiles(cab); 81 var files = Query.GetCabinetFiles(cab);
79 WixAssert.CompareLineByLine(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray()); 82 WixAssert.CompareLineByLine(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray());
@@ -81,7 +84,37 @@ namespace WixToolsetTest.CoreIntegration
81 } 84 }
82 85
83 [Fact] 86 [Fact]
84 public void CanBuildSimplePatchWithFileChanges() 87 public void CanBuildSimplePatchWithFileChangesUsingMsi()
88 {
89 var sourceFolder = TestData.Get(@"TestData", "PatchWithFileChangesUsingMsi");
90
91 using (var fs = new DisposableFileSystem())
92 {
93 var baseFolder = fs.GetFolder();
94 var tempFolderPatch = Path.Combine(baseFolder, "patch");
95
96 var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(this.templateBaselinePdb), Path.GetDirectoryName(this.templateUpdatePdb) });
97 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
98
99 var doc = GetExtractPatchXml(patchPath);
100 WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value);
101
102 var names = Query.GetSubStorageNames(patchPath);
103 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names);
104
105 var cab = Path.Combine(baseFolder, "foo.cab");
106 Query.ExtractStream(patchPath, "foo.cab", cab);
107
108 var files = Query.GetCabinetFiles(cab);
109 var file = files.Single();
110 WixAssert.StringEqual("a.txt", file.Name);
111 var contents = file.OpenText().ReadToEnd();
112 WixAssert.StringEqual("This is A v1.0.1 from the '.update-data' folder in 'PatchTemplatePackage'.\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.\r\n", contents);
113 }
114 }
115
116 [Fact]
117 public void CanBuildSimplePatchWithFileChangesUsingWixpdb()
85 { 118 {
86 var sourceFolder = TestData.Get(@"TestData", "PatchWithFileChanges"); 119 var sourceFolder = TestData.Get(@"TestData", "PatchWithFileChanges");
87 120
@@ -94,7 +127,7 @@ namespace WixToolsetTest.CoreIntegration
94 var patchPath = Path.ChangeExtension(patchPdb, ".msp"); 127 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
95 128
96 var doc = GetExtractPatchXml(patchPath); 129 var doc = GetExtractPatchXml(patchPath);
97 WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); 130 WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value);
98 131
99 var names = Query.GetSubStorageNames(patchPath); 132 var names = Query.GetSubStorageNames(patchPath);
100 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); 133 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names);
@@ -106,11 +139,73 @@ namespace WixToolsetTest.CoreIntegration
106 var file = files.Single(); 139 var file = files.Single();
107 WixAssert.StringEqual("a.txt", file.Name); 140 WixAssert.StringEqual("a.txt", file.Name);
108 var contents = file.OpenText().ReadToEnd(); 141 var contents = file.OpenText().ReadToEnd();
109 WixAssert.StringEqual("This is A v1.0.1\r\n", contents); 142 WixAssert.StringEqual("This is A v1.0.1 from the '.update-data' folder in 'PatchTemplatePackage'.\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.\r\n", contents);
110 } 143 }
111 } 144 }
112 145
113 [Fact] 146 [Fact]
147 public void CanBuildSimplePatchWithFileChangesUsingWixpdbAndAlternativeUpdatedSourceFolder()
148 {
149 var sourceFolder = TestData.Get(@"TestData", "PatchWithFileChanges");
150
151 using (var fs = new DisposableFileSystem())
152 {
153 var baseFolder = fs.GetFolder();
154 var tempFolderPatch = Path.Combine(baseFolder, "patch");
155
156 var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(this.templateBaselinePdb), Path.GetDirectoryName(this.templateUpdatePdb) }, updateBindpaths: new[] { Path.Combine(this.templateSourceFolder, ".update-data-alternative") });
157 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
158
159 var doc = GetExtractPatchXml(patchPath);
160 WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value);
161
162 var names = Query.GetSubStorageNames(patchPath);
163 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names);
164
165 var cab = Path.Combine(baseFolder, "foo.cab");
166 Query.ExtractStream(patchPath, "foo.cab", cab);
167
168 var files = Query.GetCabinetFiles(cab);
169 var file = files.Single();
170 WixAssert.StringEqual("a.txt", file.Name);
171 var contents = file.OpenText().ReadToEnd();
172 WixAssert.StringEqual("This is A v1.0.1 from the '.update-data-alternative' folder in 'PatchTemplatePackage'.\r\n\r\nDiam quis enim lobortis scelerisque fermentum dui faucibus in ornare.\r\n", contents);
173 }
174 }
175
176 [Fact]
177 public void CanBuildPatchFromAdminImage()
178 {
179 var sourceFolder = TestData.Get(@"TestData", "PatchUsingAdminImages");
180
181 var baseFolder = this.tempFileSystem.GetFolder();
182 var tempFolderPatch = Path.Combine(baseFolder, "patch");
183 var adminBaselineFolder = Path.Combine(baseFolder, "admin-baseline");
184 var adminUpdateFolder = Path.Combine(baseFolder, "admin-update");
185
186 CreateAdminImage(this.templateBaselinePdb, adminBaselineFolder);
187 CreateAdminImage(this.templateUpdatePdb, adminUpdateFolder);
188
189 var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { adminBaselineFolder, adminUpdateFolder });
190 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
191
192 var doc = GetExtractPatchXml(patchPath);
193 WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value);
194
195 var names = Query.GetSubStorageNames(patchPath);
196 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names);
197
198 var cab = Path.Combine(baseFolder, "foo.cab");
199 Query.ExtractStream(patchPath, "foo.cab", cab);
200
201 var files = Query.GetCabinetFiles(cab);
202 var file = files.Single();
203 WixAssert.StringEqual("a.txt", file.Name);
204 var contents = file.OpenText().ReadToEnd();
205 WixAssert.StringEqual("This is A v1.0.1 from the '.update-data' folder in 'PatchTemplatePackage'.\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.\r\n", contents);
206 }
207
208 [Fact]
114 public void CanBuildSimplePatchWithNoFileChanges() 209 public void CanBuildSimplePatchWithNoFileChanges()
115 { 210 {
116 var folder = TestData.Get(@"TestData", "PatchNoFileChanges"); 211 var folder = TestData.Get(@"TestData", "PatchNoFileChanges");
@@ -123,14 +218,13 @@ namespace WixToolsetTest.CoreIntegration
123 var patchPath = Path.ChangeExtension(patchPdb, ".msp"); 218 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
124 219
125 var doc = GetExtractPatchXml(patchPath); 220 var doc = GetExtractPatchXml(patchPath);
126 WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); 221 WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value);
127 222
128 var names = Query.GetSubStorageNames(patchPath); 223 var names = Query.GetSubStorageNames(patchPath);
129 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); 224 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names);
130 225
131 var cab = Path.Combine(tempFolder, "foo.cab"); 226 var cab = Path.Combine(tempFolder, "foo.cab");
132 Query.ExtractStream(patchPath, "foo.cab", cab); 227 Query.ExtractStream(patchPath, "foo.cab", cab);
133 Assert.True(File.Exists(cab));
134 228
135 var files = Query.GetCabinetFiles(cab); 229 var files = Query.GetCabinetFiles(cab);
136 Assert.Empty(files); 230 Assert.Empty(files);
@@ -155,14 +249,13 @@ namespace WixToolsetTest.CoreIntegration
155 var patchPath = Path.ChangeExtension(patchPdb, ".msp"); 249 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
156 250
157 var doc = GetExtractPatchXml(patchPath); 251 var doc = GetExtractPatchXml(patchPath);
158 WixAssert.StringEqual("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); 252 WixAssert.StringEqual("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(TargetProductCodeName).Value);
159 253
160 var names = Query.GetSubStorageNames(patchPath); 254 var names = Query.GetSubStorageNames(patchPath);
161 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names); 255 WixAssert.CompareLineByLine(new[] { "#RTM.1", "RTM.1" }, names);
162 256
163 var cab = Path.Combine(baseFolder, "foo.cab"); 257 var cab = Path.Combine(baseFolder, "foo.cab");
164 Query.ExtractStream(patchPath, "foo.cab", cab); 258 Query.ExtractStream(patchPath, "foo.cab", cab);
165 Assert.True(File.Exists(cab));
166 259
167 var files = Query.GetCabinetFiles(cab); 260 var files = Query.GetCabinetFiles(cab);
168 var file = files.Single(); 261 var file = files.Single();
@@ -197,7 +290,7 @@ namespace WixToolsetTest.CoreIntegration
197 var patchPath = Path.ChangeExtension(patchPdb, ".msp"); 290 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
198 291
199 var doc = GetExtractPatchXml(patchPath); 292 var doc = GetExtractPatchXml(patchPath);
200 WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); 293 WixAssert.StringEqual("{11111111-2222-3333-4444-555555555555}", doc.Root.Element(TargetProductCodeName).Value);
201 294
202 var names = Query.GetSubStorageNames(patchPath); 295 var names = Query.GetSubStorageNames(patchPath);
203 WixAssert.CompareLineByLine(new[] { "#ThisBaseLineIdIsTooLongAndGe.1", "ThisBaseLineIdIsTooLongAndGe.1" }, names); 296 WixAssert.CompareLineByLine(new[] { "#ThisBaseLineIdIsTooLongAndGe.1", "ThisBaseLineIdIsTooLongAndGe.1" }, names);
@@ -217,12 +310,12 @@ namespace WixToolsetTest.CoreIntegration
217 var tempFolderPatch = Path.Combine(baseFolder, "patch"); 310 var tempFolderPatch = Path.Combine(baseFolder, "patch");
218 311
219 var baselinePdb = BuildMsi("Baseline.msi", sourceFolder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0"); 312 var baselinePdb = BuildMsi("Baseline.msi", sourceFolder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0");
220 var update1Pdb = BuildMsi("Update.msi", sourceFolder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1"); 313 var updatePdb = BuildMsi("Update.msi", sourceFolder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1");
221 var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(update1Pdb) }, hasNoFiles: true); 314 var patchPdb = BuildMsp("Patch1.msp", sourceFolder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(updatePdb) }, hasNoFiles: true);
222 var patchPath = Path.ChangeExtension(patchPdb, ".msp"); 315 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
223 316
224 Assert.True(File.Exists(baselinePdb)); 317 var doc = GetExtractPatchXml(patchPath);
225 Assert.True(File.Exists(update1Pdb)); 318 WixAssert.StringEqual("{7C871EC1-1F89-4850-A6A9-D7A4C21769F6}", doc.Root.Element(TargetProductCodeName).Value);
226 } 319 }
227 } 320 }
228 321
@@ -317,7 +410,28 @@ namespace WixToolsetTest.CoreIntegration
317 return Path.ChangeExtension(outputPath, ".wixpdb"); 410 return Path.ChangeExtension(outputPath, ".wixpdb");
318 } 411 }
319 412
320 private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable<string> bindpaths = null, bool hasNoFiles = false, bool warningsAsErrors = true) 413 private static string BuildMst(string transformName, string baseFolder, string templateBaselinePdb, string templateUpdatePdb)
414 {
415 var outputPath = Path.Combine(baseFolder, transformName);
416
417 var args = new List<string>
418 {
419 "msi", "transform",
420 templateBaselinePdb,
421 templateUpdatePdb,
422 "-intermediateFolder", Path.Combine(baseFolder),
423 "-t", "patch",
424 "-o", outputPath,
425 };
426
427 var result = WixRunner.Execute(args.ToArray());
428
429 result.AssertSuccess();
430
431 return outputPath;
432 }
433
434 private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable<string> bindpaths = null, IEnumerable<string> targetBindpaths = null, IEnumerable<string> updateBindpaths = null, bool hasNoFiles = false, bool warningsAsErrors = true)
321 { 435 {
322 var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); 436 var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName));
323 437
@@ -332,10 +446,22 @@ namespace WixToolsetTest.CoreIntegration
332 "-o", outputPath 446 "-o", outputPath
333 }; 447 };
334 448
335 foreach (var additionaBindPath in bindpaths ?? Enumerable.Empty<string>()) 449 foreach (var additionalBindPath in bindpaths ?? Enumerable.Empty<string>())
336 { 450 {
337 args.Add("-bindpath"); 451 args.Add("-bindpath");
338 args.Add(additionaBindPath); 452 args.Add(additionalBindPath);
453 }
454
455 foreach (var targetBindPath in targetBindpaths ?? Enumerable.Empty<string>())
456 {
457 args.Add("-bindpath:target");
458 args.Add(targetBindPath);
459 }
460
461 foreach (var updateBindpath in updateBindpaths ?? Enumerable.Empty<string>())
462 {
463 args.Add("-bindpath:update");
464 args.Add(updateBindpath);
339 } 465 }
340 466
341 var result = WixRunner.Execute(warningsAsErrors, args.ToArray()); 467 var result = WixRunner.Execute(warningsAsErrors, args.ToArray());
@@ -365,6 +491,16 @@ namespace WixToolsetTest.CoreIntegration
365 return Path.ChangeExtension(outputPath, ".wixpdb"); 491 return Path.ChangeExtension(outputPath, ".wixpdb");
366 } 492 }
367 493
494 private static void CreateAdminImage(string msiPath, string targetDir)
495 {
496 var args = $"/a {Path.ChangeExtension(msiPath, "msi")} TARGETDIR={targetDir} /qn";
497
498 var proc = Process.Start("msiexec.exe", args);
499 proc.WaitForExit(5000);
500
501 Assert.Equal(0, proc.ExitCode);
502 }
503
368 private static XDocument GetExtractPatchXml(string path) 504 private static XDocument GetExtractPatchXml(string path)
369 { 505 {
370 var buffer = new StringBuilder(65535); 506 var buffer = new StringBuilder(65535);
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.baseline-data/A.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.baseline-data/A.txt
index 6fd385bd..f6c69979 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.baseline-data/A.txt
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.baseline-data/A.txt
@@ -1 +1 @@
This is A v1.0.0 This is A v1.0.0 from the "PatchTemplatePackage\.baseline-data" folder.
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data-alternative/A.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data-alternative/A.txt
new file mode 100644
index 00000000..17e8c861
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data-alternative/A.txt
@@ -0,0 +1,3 @@
1This is A v1.0.1 from the '.update-data-alternative' folder in 'PatchTemplatePackage'.
2
3Diam quis enim lobortis scelerisque fermentum dui faucibus in ornare.
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data/A.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data/A.txt
index f7ec8b4e..26832a47 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data/A.txt
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchTemplatePackage/.update-data/A.txt
@@ -1 +1,3 @@
1This is A v1.0.1 1This is A v1.0.1 from the '.update-data' folder in 'PatchTemplatePackage'.
2
3Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchUsingAdminImages/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchUsingAdminImages/Patch.wxs
new file mode 100644
index 00000000..f9ea1f5d
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchUsingAdminImages/Patch.wxs
@@ -0,0 +1,16 @@
1<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
2 <Patch
3 AllowRemoval="yes"
4 DisplayName="~Test Patch v$(V)"
5 Description="~Test Small Update Patch v$(V)"
6 MoreInfoURL="http://www.example.com/"
7 Manufacturer="Example Corporation"
8 Classification="Update">
9
10 <Media Id="1" Cabinet="foo.cab">
11 <PatchBaseline Id="RTM" BaselineFile="Baseline.msi" UpdateFile="Update.msi" />
12 </Media>
13
14 <PatchFamily Id='SequenceFamily' Version='$(V)' />
15 </Patch>
16</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchWithFileChangesUsingMsi/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchWithFileChangesUsingMsi/Patch.wxs
new file mode 100644
index 00000000..f9ea1f5d
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchWithFileChangesUsingMsi/Patch.wxs
@@ -0,0 +1,16 @@
1<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
2 <Patch
3 AllowRemoval="yes"
4 DisplayName="~Test Patch v$(V)"
5 Description="~Test Small Update Patch v$(V)"
6 MoreInfoURL="http://www.example.com/"
7 Manufacturer="Example Corporation"
8 Classification="Update">
9
10 <Media Id="1" Cabinet="foo.cab">
11 <PatchBaseline Id="RTM" BaselineFile="Baseline.msi" UpdateFile="Update.msi" />
12 </Media>
13
14 <PatchFamily Id='SequenceFamily' Version='$(V)' />
15 </Patch>
16</Wix>