aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2018-08-11 01:06:40 -0700
committerRob Mensching <rob@firegiant.com>2018-08-11 01:06:40 -0700
commit2a27f9032aa115bc2f88d9be695d9b049db1a894 (patch)
tree7882bd4c94da118a6fc655a3c22e1dd0cf3b60e1
parentb54516035b4ebbfbd8899b26de501bfa13f53e8b (diff)
downloadwix-2a27f9032aa115bc2f88d9be695d9b049db1a894.tar.gz
wix-2a27f9032aa115bc2f88d9be695d9b049db1a894.tar.bz2
wix-2a27f9032aa115bc2f88d9be695d9b049db1a894.zip
Track files to enable clean builds in MSBuild
-rw-r--r--src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs2
-rw-r--r--src/WixToolset.Core.Burn/BundleBackend.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs52
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs14
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs62
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs27
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs18
-rw-r--r--src/WixToolset.Core.WindowsInstaller/MsiBackend.cs8
-rw-r--r--src/WixToolset.Core.WindowsInstaller/MsmBackend.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs2
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs3
-rw-r--r--src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs12
-rw-r--r--src/WixToolset.Core/FileTransfer.cs2
-rw-r--r--src/WixToolset.Core/Layout.cs140
-rw-r--r--src/WixToolset.Core/LayoutContext.cs2
-rw-r--r--src/WixToolset.Core/TrackedFile.cs26
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs4
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs12
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs2
20 files changed, 284 insertions, 110 deletions
diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
index bd282f54..5dfe935f 100644
--- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
@@ -101,7 +101,7 @@ namespace WixToolset.Core.Burn
101 101
102 public IEnumerable<IFileTransfer> FileTransfers { get; private set; } 102 public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
103 103
104 public IEnumerable<string> ContentFilePaths { get; private set; } 104 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; }
105 105
106 public void Execute() 106 public void Execute()
107 { 107 {
diff --git a/src/WixToolset.Core.Burn/BundleBackend.cs b/src/WixToolset.Core.Burn/BundleBackend.cs
index 63504df2..96a35b14 100644
--- a/src/WixToolset.Core.Burn/BundleBackend.cs
+++ b/src/WixToolset.Core.Burn/BundleBackend.cs
@@ -24,7 +24,7 @@ namespace WixToolset.Core.Burn
24 //command.WixVariableResolver = context.WixVariableResolver; 24 //command.WixVariableResolver = context.WixVariableResolver;
25 command.Execute(); 25 command.Execute();
26 26
27 return new BindResult { FileTransfers = command.FileTransfers, ContentFilePaths = command.ContentFilePaths }; 27 return new BindResult { FileTransfers = command.FileTransfers, TrackedFiles = command.TrackedFiles };
28 } 28 }
29 29
30 public bool Inscribe(IInscribeContext context) 30 public bool Inscribe(IInscribeContext context)
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 9194c4c7..a7da13bb 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -39,6 +39,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
39 this.FileSystemExtensions = context.FileSystemExtensions; 39 this.FileSystemExtensions = context.FileSystemExtensions;
40 this.Intermediate = context.IntermediateRepresentation; 40 this.Intermediate = context.IntermediateRepresentation;
41 this.OutputPath = context.OutputPath; 41 this.OutputPath = context.OutputPath;
42 this.OutputPdbPath = context.OutputPdbPath;
42 this.IntermediateFolder = context.IntermediateFolder; 43 this.IntermediateFolder = context.IntermediateFolder;
43 this.Validator = validator; 44 this.Validator = validator;
44 45
@@ -71,6 +72,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
71 72
72 private string OutputPath { get; } 73 private string OutputPath { get; }
73 74
75 private string OutputPdbPath { get; }
76
74 private bool SuppressAddingValidationRows { get; } 77 private bool SuppressAddingValidationRows { get; }
75 78
76 private bool SuppressLayout { get; } 79 private bool SuppressLayout { get; }
@@ -83,7 +86,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
83 86
84 public IEnumerable<IFileTransfer> FileTransfers { get; private set; } 87 public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
85 88
86 public IEnumerable<string> ContentFilePaths { get; private set; } 89 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; }
87 90
88 public Pdb Pdb { get; private set; } 91 public Pdb Pdb { get; private set; }
89 92
@@ -92,6 +95,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
92 var section = this.Intermediate.Sections.Single(); 95 var section = this.Intermediate.Sections.Single();
93 96
94 var fileTransfers = new List<IFileTransfer>(); 97 var fileTransfers = new List<IFileTransfer>();
98 var trackedFiles = new List<ITrackedFile>();
95 99
96 var containsMergeModules = false; 100 var containsMergeModules = false;
97 var suppressedTableNames = new HashSet<string>(); 101 var suppressedTableNames = new HashSet<string>();
@@ -396,6 +400,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
396 command.Execute(); 400 command.Execute();
397 401
398 fileTransfers.AddRange(command.FileTransfers); 402 fileTransfers.AddRange(command.FileTransfers);
403 trackedFiles.AddRange(command.TrackedFiles);
399 } 404 }
400 405
401#if TODO_FINISH_PATCH 406#if TODO_FINISH_PATCH
@@ -416,11 +421,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
416 421
417 // Generate database file. 422 // Generate database file.
418 this.Messaging.Write(VerboseMessages.GeneratingDatabase()); 423 this.Messaging.Write(VerboseMessages.GeneratingDatabase());
419 string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath));
420 this.GenerateDatabase(output, tempDatabaseFile, false, false);
421 424
422 var transfer = this.BackendHelper.CreateFileTransfer(tempDatabaseFile, this.OutputPath, true, FileTransferType.Built); // note where this database needs to move in the future 425 var trackMsi = this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final);
423 fileTransfers.Add(transfer); 426 trackedFiles.Add(trackMsi);
427
428 var temporaryFiles = this.GenerateDatabase(output, trackMsi.Path, false, false);
429 trackedFiles.AddRange(temporaryFiles);
424 430
425 // Stop processing if an error previously occurred. 431 // Stop processing if an error previously occurred.
426 if (this.Messaging.EncounteredError) 432 if (this.Messaging.EncounteredError)
@@ -455,7 +461,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
455 var command = new MergeModulesCommand(); 461 var command = new MergeModulesCommand();
456 command.FileFacades = fileFacades; 462 command.FileFacades = fileFacades;
457 command.Output = output; 463 command.Output = output;
458 command.OutputPath = tempDatabaseFile; 464 command.OutputPath = this.OutputPath;
459 command.SuppressedTableNames = suppressedTableNames; 465 command.SuppressedTableNames = suppressedTableNames;
460 command.Execute(); 466 command.Execute();
461 } 467 }
@@ -476,7 +482,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
476 482
477 Messaging.Instance.Write(WixVerboses.ValidatingDatabase()); 483 Messaging.Instance.Write(WixVerboses.ValidatingDatabase());
478 484
479 this.Validator.Validate(tempDatabaseFile); 485 this.Validator.Validate(this.OutputPath);
480 486
481 stopwatch.Stop(); 487 stopwatch.Stop();
482 Messaging.Instance.Write(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); 488 Messaging.Instance.Write(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds));
@@ -498,19 +504,36 @@ namespace WixToolset.Core.WindowsInstaller.Bind
498 command.LayoutDirectory = layoutDirectory; 504 command.LayoutDirectory = layoutDirectory;
499 command.LongNamesInImage = longNames; 505 command.LongNamesInImage = longNames;
500 command.ResolveMedia = this.ResolveMedia; 506 command.ResolveMedia = this.ResolveMedia;
501 command.DatabasePath = tempDatabaseFile; 507 command.DatabasePath = this.OutputPath;
502 command.Execute(); 508 command.Execute();
503 509
504 fileTransfers.AddRange(command.FileTransfers); 510 fileTransfers.AddRange(command.FileTransfers);
511 trackedFiles.AddRange(command.TrackedFiles);
505 } 512 }
506 513
507 this.FileTransfers = fileTransfers;
508 this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source.Path).ToList();
509 this.Pdb = new Pdb { Output = output }; 514 this.Pdb = new Pdb { Output = output };
510 515
516 if (!String.IsNullOrEmpty(this.OutputPdbPath))
517 {
518 var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final);
519 trackedFiles.Add(trackPdb);
520
521 this.Pdb.Save(trackPdb.Path);
522 }
523
524 this.FileTransfers = fileTransfers;
525 // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables).
526 trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.WixFile.Source.Path, TrackedFileType.Input, f.File.SourceLineNumbers)));
527 this.TrackedFiles = trackedFiles;
528
511 // TODO: Eventually this gets removed 529 // TODO: Eventually this gets removed
512 var intermediate = new Intermediate(this.Intermediate.Id, new[] { section }, this.Intermediate.Localizations.ToDictionary(l => l.Culture, StringComparer.OrdinalIgnoreCase), this.Intermediate.EmbedFilePaths); 530 var intermediate = new Intermediate(this.Intermediate.Id, new[] { section }, this.Intermediate.Localizations.ToDictionary(l => l.Culture, StringComparer.OrdinalIgnoreCase), this.Intermediate.EmbedFilePaths);
513 intermediate.Save(Path.ChangeExtension(this.OutputPath, "wir")); 531 var trackIntermediate = this.BackendHelper.TrackFile(Path.Combine(this.IntermediateFolder, Path.GetFileName(Path.ChangeExtension(this.OutputPath, "wir"))), TrackedFileType.Intermediate);
532 intermediate.Save(trackIntermediate.Path);
533 trackedFiles.Add(trackIntermediate);
534
535 //transfer = this.BackendHelper.CreateFileTransfer(intermediatePath, Path.ChangeExtension(this.OutputPath, "wir"), true, FileTransferType.Built);
536 //fileTransfers.Add(transfer);
514 } 537 }
515 538
516#if TODO_FINISH_PATCH 539#if TODO_FINISH_PATCH
@@ -881,9 +904,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
881 /// <param name="databaseFile">The database file to create.</param> 904 /// <param name="databaseFile">The database file to create.</param>
882 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param> 905 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
883 /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param> 906 /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param>
884 private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) 907 private IEnumerable<ITrackedFile> GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
885 { 908 {
886 var command = new GenerateDatabaseCommand(); 909 var command = new GenerateDatabaseCommand();
910 command.BackendHelper = this.BackendHelper;
887 command.Extensions = this.FileSystemExtensions; 911 command.Extensions = this.FileSystemExtensions;
888 command.Output = output; 912 command.Output = output;
889 command.OutputPath = databaseFile; 913 command.OutputPath = databaseFile;
@@ -891,9 +915,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
891 command.UseSubDirectory = useSubdirectory; 915 command.UseSubDirectory = useSubdirectory;
892 command.SuppressAddingValidationRows = this.SuppressAddingValidationRows; 916 command.SuppressAddingValidationRows = this.SuppressAddingValidationRows;
893 command.TableDefinitions = this.TableDefinitions; 917 command.TableDefinitions = this.TableDefinitions;
894 command.TempFilesLocation = this.IntermediateFolder; 918 command.IntermediateFolder = this.IntermediateFolder;
895 command.Codepage = this.Codepage; 919 command.Codepage = this.Codepage;
896 command.Execute(); 920 command.Execute();
921
922 return command.GeneratedTemporaryFiles;
897 } 923 }
898 } 924 }
899} 925}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
index 8cb0e0de..3f5b9f05 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
@@ -465,7 +465,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
465 command.Output = output; 465 command.Output = output;
466 command.OutputPath = outputPath; 466 command.OutputPath = outputPath;
467 command.TableDefinitions = this.TableDefinitions; 467 command.TableDefinitions = this.TableDefinitions;
468 command.TempFilesLocation = this.TempFilesLocation; 468 command.IntermediateFolder = this.TempFilesLocation;
469 command.SuppressAddingValidationRows = true; 469 command.SuppressAddingValidationRows = true;
470 command.UseSubDirectory = true; 470 command.UseSubDirectory = true;
471 command.Execute(); 471 command.Execute();
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs
index 759fa586..541dacf8 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs
@@ -48,7 +48,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
48 // to be built to and check if there is a cabinet that can be reused. 48 // to be built to and check if there is a cabinet that can be reused.
49 if (!String.IsNullOrEmpty(this.CabCachePath)) 49 if (!String.IsNullOrEmpty(this.CabCachePath))
50 { 50 {
51 string cabinetName = Path.GetFileName(cabinetPath); 51 var cabinetName = Path.GetFileName(cabinetPath);
52 resolved.Path = Path.Combine(this.CabCachePath, cabinetName); 52 resolved.Path = Path.Combine(this.CabCachePath, cabinetName);
53 53
54 if (CheckFileExists(resolved.Path)) 54 if (CheckFileExists(resolved.Path))
@@ -57,10 +57,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
57 // 1. any files are added or removed 57 // 1. any files are added or removed
58 // 2. order of files changed or names changed 58 // 2. order of files changed or names changed
59 // 3. modified time changed 59 // 3. modified time changed
60 bool cabinetValid = true; 60 var cabinetValid = true;
61 61
62 var cabinet = new Cabinet(resolved.Path); 62 var cabinet = new Cabinet(resolved.Path);
63 List<CabinetFileInfo> fileList = cabinet.Enumerate(); 63 var fileList = cabinet.Enumerate();
64 64
65 if (filesWithPath.Count() != fileList.Count) 65 if (filesWithPath.Count() != fileList.Count)
66 { 66 {
@@ -68,16 +68,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
68 } 68 }
69 else 69 else
70 { 70 {
71 int i = 0; 71 var i = 0;
72 foreach (BindFileWithPath file in filesWithPath) 72 foreach (var file in filesWithPath)
73 { 73 {
74 // First check that the file identifiers match because that is quick and easy. 74 // First check that the file identifiers match because that is quick and easy.
75 CabinetFileInfo cabFileInfo = fileList[i]; 75 var cabFileInfo = fileList[i];
76 cabinetValid = (cabFileInfo.FileId == file.Id); 76 cabinetValid = (cabFileInfo.FileId == file.Id);
77 if (cabinetValid) 77 if (cabinetValid)
78 { 78 {
79 // Still valid so ensure the file sizes are the same. 79 // Still valid so ensure the file sizes are the same.
80 FileInfo fileInfo = new FileInfo(file.Path); 80 var fileInfo = new FileInfo(file.Path);
81 cabinetValid = (cabFileInfo.Size == fileInfo.Length); 81 cabinetValid = (cabFileInfo.Size == fileInfo.Length);
82 if (cabinetValid) 82 if (cabinetValid)
83 { 83 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
index ed8f0ece..a85312c4 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
@@ -28,6 +28,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
28 28
29 private List<IFileTransfer> fileTransfers; 29 private List<IFileTransfer> fileTransfers;
30 30
31 private List<ITrackedFile> trackedFiles;
32
31 private FileSplitCabNamesCallback newCabNamesCallBack; 33 private FileSplitCabNamesCallback newCabNamesCallBack;
32 34
33 private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence 35 private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence
@@ -36,6 +38,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
36 { 38 {
37 this.fileTransfers = new List<IFileTransfer>(); 39 this.fileTransfers = new List<IFileTransfer>();
38 40
41 this.trackedFiles = new List<ITrackedFile>();
42
39 this.newCabNamesCallBack = this.NewCabNamesCallBack; 43 this.newCabNamesCallBack = this.NewCabNamesCallBack;
40 44
41 this.BackendHelper = backendHelper; 45 this.BackendHelper = backendHelper;
@@ -78,8 +82,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
78 82
79 public IEnumerable<IFileTransfer> FileTransfers => this.fileTransfers; 83 public IEnumerable<IFileTransfer> FileTransfers => this.fileTransfers;
80 84
85 public IEnumerable<ITrackedFile> TrackedFiles => this.trackedFiles;
86
81 /// <param name="output">Output to generate image for.</param> 87 /// <param name="output">Output to generate image for.</param>
82 /// <param name="fileTransfers">Array of files to be transfered.</param>
83 /// <param name="layoutDirectory">The directory in which the image should be layed out.</param> 88 /// <param name="layoutDirectory">The directory in which the image should be layed out.</param>
84 /// <param name="compressed">Flag if source image should be compressed.</param> 89 /// <param name="compressed">Flag if source image should be compressed.</param>
85 /// <returns>The uncompressed file rows.</returns> 90 /// <returns>The uncompressed file rows.</returns>
@@ -119,7 +124,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
119 124
120 string cabinetDir = this.ResolveMedia(mediaTuple, mediaLayoutFolder, this.LayoutDirectory); 125 string cabinetDir = this.ResolveMedia(mediaTuple, mediaLayoutFolder, this.LayoutDirectory);
121 126
122 CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaTuple, compressionLevel, files, this.fileTransfers); 127 var cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaTuple, compressionLevel, files);
123 if (null != cabinetWorkItem) 128 if (null != cabinetWorkItem)
124 { 129 {
125 cabinetBuilder.Enqueue(cabinetWorkItem); 130 cabinetBuilder.Enqueue(cabinetWorkItem);
@@ -188,9 +193,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
188 /// <param name="cabinetDir">Directory to create cabinet in.</param> 193 /// <param name="cabinetDir">Directory to create cabinet in.</param>
189 /// <param name="mediaRow">MediaRow containing information about the cabinet.</param> 194 /// <param name="mediaRow">MediaRow containing information about the cabinet.</param>
190 /// <param name="fileFacades">Collection of files in this cabinet.</param> 195 /// <param name="fileFacades">Collection of files in this cabinet.</param>
191 /// <param name="fileTransfers">Array of files to be transfered.</param>
192 /// <returns>created CabinetWorkItem object</returns> 196 /// <returns>created CabinetWorkItem object</returns>
193 private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaTuple mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<IFileTransfer> fileTransfers) 197 private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaTuple mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades)
194 { 198 {
195 CabinetWorkItem cabinetWorkItem = null; 199 CabinetWorkItem cabinetWorkItem = null;
196 string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet); 200 string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet);
@@ -219,7 +223,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
219 223
220 var cabinetResolver = new CabinetResolver(this.CabCachePath, this.BackendExtensions); 224 var cabinetResolver = new CabinetResolver(this.CabCachePath, this.BackendExtensions);
221 225
222 ResolvedCabinet resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades); 226 var resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades);
223 227
224 // create a cabinet work item if it's not being skipped 228 // create a cabinet work item if it's not being skipped
225 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) 229 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
@@ -248,19 +252,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind
248 } 252 }
249 } 253 }
250 254
255 var trackResolvedCabinet = this.BackendHelper.TrackFile(resolvedCabinet.Path, TrackedFileType.Intermediate, mediaRow.SourceLineNumbers);
256 this.trackedFiles.Add(trackResolvedCabinet);
257
251 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) 258 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
252 { 259 {
253 Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]); 260 var streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]);
254 261
255 Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers); 262 var streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers);
256 streamRow[0] = mediaRow.Cabinet.Substring(1); 263 streamRow[0] = mediaRow.Cabinet.Substring(1);
257 streamRow[1] = resolvedCabinet.Path; 264 streamRow[1] = resolvedCabinet.Path;
258 } 265 }
259 else 266 else
260 { 267 {
261 var destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet); 268 var trackDestination = this.BackendHelper.TrackFile(Path.Combine(cabinetDir, mediaRow.Cabinet), TrackedFileType.Final, mediaRow.SourceLineNumbers);
262 var transfer = this.BackendHelper.CreateFileTransfer(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, FileTransferType.Built, mediaRow.SourceLineNumbers); 269 this.trackedFiles.Add(trackDestination);
263 fileTransfers.Add(transfer); 270
271 var transfer = this.BackendHelper.CreateFileTransfer(resolvedCabinet.Path, trackDestination.Path, resolvedCabinet.BuildOption == CabinetBuildOption.BuildAndMove, mediaRow.SourceLineNumbers);
272 this.fileTransfers.Add(transfer);
264 } 273 }
265 274
266 return cabinetWorkItem; 275 return cabinetWorkItem;
@@ -298,10 +307,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
298 /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param> 307 /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param>
299 /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param> 308 /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param>
300 /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param> 309 /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param>
301 internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) 310 internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken)
302 { 311 {
303 // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads 312 // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads
304 Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); 313 var mutex = new Mutex(false, "WixCabinetSplitBinderCallback");
305 try 314 try
306 { 315 {
307 if (!mutex.WaitOne(0, false)) // Check if you can get the lock 316 if (!mutex.WaitOne(0, false)) // Check if you can get the lock
@@ -311,19 +320,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind
311 mutex.WaitOne(); // Wait on other thread 320 mutex.WaitOne(); // Wait on other thread
312 } 321 }
313 322
314 string firstCabinetName = firstCabName + ".cab"; 323 var firstCabinetName = firstCabName + ".cab";
315 string newCabinetName = newCabName; 324 var transferAdded = false; // Used for Error Handling
316 bool transferAdded = false; // Used for Error Handling
317 325
318 // Create File Transfer for new Cabinet using transfer of Base Cabinet 326 // Create File Transfer for new Cabinet using transfer of Base Cabinet
319 foreach (var transfer in this.FileTransfers) 327 foreach (var transfer in this.FileTransfers)
320 { 328 {
321 if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) 329 if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase))
322 { 330 {
323 string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); 331 var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName);
324 string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); 332 var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName);
333
334 var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers);
335 this.trackedFiles.Add(trackSource);
336
337 var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers);
338 this.trackedFiles.Add(trackTarget);
325 339
326 var newTransfer = this.BackendHelper.CreateFileTransfer(newCabSourcePath, newCabTargetPath, transfer.Move, FileTransferType.Built, transfer.SourceLineNumbers); 340 var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers);
327 this.fileTransfers.Add(newTransfer); 341 this.fileTransfers.Add(newTransfer);
328 342
329 transferAdded = true; 343 transferAdded = true;
@@ -338,13 +352,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
338 } 352 }
339 353
340 // Add the new Cabinets to media table using LastSequence of Base Cabinet 354 // Add the new Cabinets to media table using LastSequence of Base Cabinet
341 Table mediaTable = this.Output.Tables["Media"]; 355 var mediaTable = this.Output.Tables["Media"];
342 Table wixFileTable = this.Output.Tables["WixFile"]; 356 var wixFileTable = this.Output.Tables["WixFile"];
343 int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain 357 var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain
344 int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain 358 var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain
345 bool lastSplitCabinetFound = false; // Used for Error Handling 359 var lastSplitCabinetFound = false; // Used for Error Handling
346 360
347 string lastCabinetOfThisSequence = String.Empty; 361 var lastCabinetOfThisSequence = String.Empty;
348 // Get the Value of Last Cabinet Added in this split Sequence from Dictionary 362 // Get the Value of Last Cabinet Added in this split Sequence from Dictionary
349 if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) 363 if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence))
350 { 364 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
index 3ab3b601..4e062696 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
@@ -14,11 +14,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
14 using WixToolset.Core.Native; 14 using WixToolset.Core.Native;
15 using WixToolset.Data.WindowsInstaller; 15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Extensibility.Services; 16 using WixToolset.Extensibility.Services;
17 using WixToolset.Extensibility.Data;
17 18
18 internal class GenerateDatabaseCommand 19 internal class GenerateDatabaseCommand
19 { 20 {
20 public int Codepage { private get; set; } 21 public int Codepage { private get; set; }
21 22
23 public IBackendHelper BackendHelper { private get; set; }
24
22 public IEnumerable<IFileSystemExtension> Extensions { private get; set; } 25 public IEnumerable<IFileSystemExtension> Extensions { private get; set; }
23 26
24 /// <summary> 27 /// <summary>
@@ -34,7 +37,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
34 37
35 public TableDefinitionCollection TableDefinitions { private get; set; } 38 public TableDefinitionCollection TableDefinitions { private get; set; }
36 39
37 public string TempFilesLocation { private get; set; } 40 public string IntermediateFolder { private get; set; }
41
42 public List<ITrackedFile> GeneratedTemporaryFiles { get; } = new List<ITrackedFile>();
38 43
39 /// <summary> 44 /// <summary>
40 /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files. 45 /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.
@@ -103,7 +108,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
103 } 108 }
104 109
105 // Set the base directory. 110 // Set the base directory.
106 string baseDirectory = this.TempFilesLocation; 111 var baseDirectory = this.IntermediateFolder;
107 112
108 if (this.UseSubDirectory) 113 if (this.UseSubDirectory)
109 { 114 {
@@ -114,7 +119,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
114 Directory.CreateDirectory(baseDirectory); 119 Directory.CreateDirectory(baseDirectory);
115 } 120 }
116 121
117 var idtDirectory = Path.Combine(baseDirectory, "idts"); 122 var idtDirectory = Path.Combine(baseDirectory, "_idts");
118 Directory.CreateDirectory(idtDirectory); 123 Directory.CreateDirectory(idtDirectory);
119 124
120 try 125 try
@@ -137,6 +142,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
137 this.Output.Codepage = this.Codepage; 142 this.Output.Codepage = this.Codepage;
138 } 143 }
139 144
145 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
146
140 using (Database db = new Database(this.OutputPath, type)) 147 using (Database db = new Database(this.OutputPath, type))
141 { 148 {
142 // if we're not using the default codepage, import a new one into our 149 // if we're not using the default codepage, import a new one into our
@@ -185,6 +192,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
185 var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Output.Codepage, idtDirectory, this.KeepAddedColumns); 192 var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Output.Codepage, idtDirectory, this.KeepAddedColumns);
186 command.Execute(); 193 command.Execute();
187 194
195 var buildOutput = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary);
196 this.GeneratedTemporaryFiles.Add(buildOutput);
197
188 db.Import(command.IdtPath); 198 db.Import(command.IdtPath);
189 } 199 }
190 catch (WixInvalidIdtException) 200 catch (WixInvalidIdtException)
@@ -309,7 +319,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
309 { 319 {
310 foreach (SubStorage subStorage in this.Output.SubStorages) 320 foreach (SubStorage subStorage in this.Output.SubStorages)
311 { 321 {
312 string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst")); 322 string transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst"));
313 323
314 // Bind the transform. 324 // Bind the transform.
315 this.BindTransform(subStorage.Data, transformFile); 325 this.BindTransform(subStorage.Data, transformFile);
@@ -346,7 +356,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
346 var command = new BindTransformCommand(); 356 var command = new BindTransformCommand();
347 command.Messaging = this.Messaging; 357 command.Messaging = this.Messaging;
348 command.Extensions = this.Extensions; 358 command.Extensions = this.Extensions;
349 command.TempFilesLocation = this.TempFilesLocation; 359 command.TempFilesLocation = this.IntermediateFolder;
350 command.Transform = transform; 360 command.Transform = transform;
351 command.OutputPath = outputPath; 361 command.OutputPath = outputPath;
352 command.TableDefinitions = this.TableDefinitions; 362 command.TableDefinitions = this.TableDefinitions;
@@ -356,8 +366,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
356 private void SetDatabaseCodepage(Database db, int codepage, string idtDirectory) 366 private void SetDatabaseCodepage(Database db, int codepage, string idtDirectory)
357 { 367 {
358 // write out the _ForceCodepage IDT file 368 // write out the _ForceCodepage IDT file
359 string idtPath = Path.Combine(idtDirectory, "_ForceCodepage.idt"); 369 var idtPath = Path.Combine(idtDirectory, "_ForceCodepage.idt");
360 using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) 370 using (var idtFile = new StreamWriter(idtPath, false, Encoding.ASCII))
361 { 371 {
362 idtFile.WriteLine(); // dummy column name record 372 idtFile.WriteLine(); // dummy column name record
363 idtFile.WriteLine(); // dummy column definition record 373 idtFile.WriteLine(); // dummy column definition record
@@ -365,6 +375,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
365 idtFile.WriteLine("\t_ForceCodepage"); 375 idtFile.WriteLine("\t_ForceCodepage");
366 } 376 }
367 377
378 var trackId = this.BackendHelper.TrackFile(idtPath, TrackedFileType.Temporary);
379 this.GeneratedTemporaryFiles.Add(trackId);
380
368 // try to import the table into the MSI 381 // try to import the table into the MSI
369 try 382 try
370 { 383 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
index b09f92bb..3ad74fd1 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
@@ -43,10 +43,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
43 43
44 public IEnumerable<IFileTransfer> FileTransfers { get; private set; } 44 public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
45 45
46 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; }
47
46 public void Execute() 48 public void Execute()
47 { 49 {
48 var fileTransfers = new List<IFileTransfer>(); 50 var fileTransfers = new List<IFileTransfer>();
49 51
52 var trackedFiles = new List<ITrackedFile>();
53
50 var directories = new Dictionary<string, ResolvedDirectory>(); 54 var directories = new Dictionary<string, ResolvedDirectory>();
51 55
52 var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId); 56 var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId);
@@ -108,14 +112,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind
108 112
109 // finally put together the base media layout path and the relative file layout path 113 // finally put together the base media layout path and the relative file layout path
110 var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); 114 var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath);
111 var transfer = this.BackendHelper.CreateFileTransfer(facade.WixFile.Source.Path, fileLayoutPath, false, FileTransferType.Content, facade.File.SourceLineNumbers); 115
116 var transfer = this.BackendHelper.CreateFileTransfer(facade.WixFile.Source.Path, fileLayoutPath, false, facade.File.SourceLineNumbers);
112 fileTransfers.Add(transfer); 117 fileTransfers.Add(transfer);
118
119 // Track the location where the cabinet will be placed. If the transfer is
120 // redundant then then the file should not be cleaned. This is important
121 // because if the source and destination of the transfer is the same, we
122 // don't want to clean the file because we'd be deleting the original
123 // (and that would be bad).
124 var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.File.SourceLineNumbers);
125 tracked.Clean = !transfer.Redundant;
126
127 trackedFiles.Add(tracked);
113 } 128 }
114 } 129 }
115 } 130 }
116 } 131 }
117 132
118 this.FileTransfers = fileTransfers; 133 this.FileTransfers = fileTransfers;
134 this.TrackedFiles = trackedFiles;
119 } 135 }
120 } 136 }
121} 137}
diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs
index f72a7c66..c0c518f8 100644
--- a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs
+++ b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs
@@ -29,18 +29,12 @@ namespace WixToolset.Core.WindowsInstaller
29 var command = new BindDatabaseCommand(context, backendExtensions, validator); 29 var command = new BindDatabaseCommand(context, backendExtensions, validator);
30 command.Execute(); 30 command.Execute();
31 31
32 var result = new BindResult { FileTransfers = command.FileTransfers, ContentFilePaths = command.ContentFilePaths }; 32 var result = new BindResult { FileTransfers = command.FileTransfers, TrackedFiles = command.TrackedFiles };
33 33
34 foreach (var extension in backendExtensions) 34 foreach (var extension in backendExtensions)
35 { 35 {
36 extension.PostBackendBind(result, command.Pdb); 36 extension.PostBackendBind(result, command.Pdb);
37 } 37 }
38
39 if (!String.IsNullOrEmpty(context.OutputPdbPath))
40 {
41 command.Pdb?.Save(context.OutputPdbPath);
42 }
43
44 return result; 38 return result;
45 } 39 }
46 40
diff --git a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs
index 91377b48..6c97f08d 100644
--- a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs
+++ b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs
@@ -28,7 +28,7 @@ namespace WixToolset.Core.WindowsInstaller
28 var command = new BindDatabaseCommand(context, backendExtensions, validator); 28 var command = new BindDatabaseCommand(context, backendExtensions, validator);
29 command.Execute(); 29 command.Execute();
30 30
31 var result = new BindResult { FileTransfers = command.FileTransfers, ContentFilePaths = command.ContentFilePaths }; 31 var result = new BindResult { FileTransfers = command.FileTransfers, TrackedFiles = command.TrackedFiles };
32 32
33 foreach (var extension in backendExtensions) 33 foreach (var extension in backendExtensions)
34 { 34 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
index 80401822..3c32719e 100644
--- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
@@ -251,7 +251,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
251 command.UseSubDirectory = false; 251 command.UseSubDirectory = false;
252 command.SuppressAddingValidationRows = true; 252 command.SuppressAddingValidationRows = true;
253 command.TableDefinitions = this.TableDefinitions; 253 command.TableDefinitions = this.TableDefinitions;
254 command.TempFilesLocation = this.IntermediateFolder; 254 command.IntermediateFolder = this.IntermediateFolder;
255 command.Codepage = -1; 255 command.Codepage = -1;
256 command.Execute(); 256 command.Execute();
257 } 257 }
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs
index 5c089b5f..b460e48f 100644
--- a/src/WixToolset.Core/CommandLine/BuildCommand.cs
+++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -276,8 +276,9 @@ namespace WixToolset.Core.CommandLine
276 276
277 { 277 {
278 var layout = new Layout(this.ServiceProvider); 278 var layout = new Layout(this.ServiceProvider);
279 layout.TrackedFiles = bindResult.TrackedFiles;
279 layout.FileTransfers = bindResult.FileTransfers; 280 layout.FileTransfers = bindResult.FileTransfers;
280 layout.ContentFilePaths = bindResult.ContentFilePaths; 281 layout.IntermediateFolder = this.IntermediateFolder;
281 layout.ContentsFile = this.ContentsFile; 282 layout.ContentsFile = this.ContentsFile;
282 layout.OutputsFile = this.OutputsFile; 283 layout.OutputsFile = this.OutputsFile;
283 layout.BuiltOutputsFile = this.BuiltOutputsFile; 284 layout.BuiltOutputsFile = this.BuiltOutputsFile;
diff --git a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs
index 0cda4437..6cc91487 100644
--- a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs
+++ b/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs
@@ -19,18 +19,17 @@ namespace WixToolset.Core.ExtensibilityServices
19 19
20 private IMessaging Messaging { get; } 20 private IMessaging Messaging { get; }
21 21
22 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, FileTransferType type, SourceLineNumber sourceLineNumbers) 22 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null)
23 { 23 {
24 var sourceFullPath = GetValidatedFullPath(sourceLineNumbers, source); 24 var sourceFullPath = this.GetValidatedFullPath(sourceLineNumbers, source);
25 25
26 var destinationFullPath = GetValidatedFullPath(sourceLineNumbers, destination); 26 var destinationFullPath = this.GetValidatedFullPath(sourceLineNumbers, destination);
27 27
28 return (String.IsNullOrEmpty(sourceFullPath) || String.IsNullOrEmpty(destinationFullPath)) ? null : new FileTransfer 28 return (String.IsNullOrEmpty(sourceFullPath) || String.IsNullOrEmpty(destinationFullPath)) ? null : new FileTransfer
29 { 29 {
30 Source = sourceFullPath, 30 Source = sourceFullPath,
31 Destination = destinationFullPath, 31 Destination = destinationFullPath,
32 Move = move, 32 Move = move,
33 Type = type,
34 SourceLineNumbers = sourceLineNumbers, 33 SourceLineNumbers = sourceLineNumbers,
35 Redundant = String.Equals(sourceFullPath, destinationFullPath, StringComparison.OrdinalIgnoreCase) 34 Redundant = String.Equals(sourceFullPath, destinationFullPath, StringComparison.OrdinalIgnoreCase)
36 }; 35 };
@@ -41,6 +40,11 @@ namespace WixToolset.Core.ExtensibilityServices
41 return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); 40 return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant();
42 } 41 }
43 42
43 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null)
44 {
45 return new TrackedFile(path, type, sourceLineNumbers);
46 }
47
44 private string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path) 48 private string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path)
45 { 49 {
46 try 50 try
diff --git a/src/WixToolset.Core/FileTransfer.cs b/src/WixToolset.Core/FileTransfer.cs
index 9da406eb..7f9ed0f3 100644
--- a/src/WixToolset.Core/FileTransfer.cs
+++ b/src/WixToolset.Core/FileTransfer.cs
@@ -15,8 +15,6 @@ namespace WixToolset.Core
15 15
16 public SourceLineNumber SourceLineNumbers { get; set; } 16 public SourceLineNumber SourceLineNumbers { get; set; }
17 17
18 public FileTransferType Type { get; set; }
19
20 public bool Redundant { get; set; } 18 public bool Redundant { get; set; }
21 } 19 }
22} 20}
diff --git a/src/WixToolset.Core/Layout.cs b/src/WixToolset.Core/Layout.cs
index 128efc61..b1b03aa7 100644
--- a/src/WixToolset.Core/Layout.cs
+++ b/src/WixToolset.Core/Layout.cs
@@ -28,9 +28,11 @@ namespace WixToolset.Core
28 28
29 private IMessaging Messaging { get; } 29 private IMessaging Messaging { get; }
30 30
31 public IEnumerable<ITrackedFile> TrackedFiles { get; set; }
32
31 public IEnumerable<IFileTransfer> FileTransfers { get; set; } 33 public IEnumerable<IFileTransfer> FileTransfers { get; set; }
32 34
33 public IEnumerable<string> ContentFilePaths { get; set; } 35 public string IntermediateFolder { get; set; }
34 36
35 public string ContentsFile { get; set; } 37 public string ContentsFile { get; set; }
36 38
@@ -46,8 +48,8 @@ namespace WixToolset.Core
46 48
47 var context = this.ServiceProvider.GetService<ILayoutContext>(); 49 var context = this.ServiceProvider.GetService<ILayoutContext>();
48 context.Extensions = extensionManager.Create<ILayoutExtension>(); 50 context.Extensions = extensionManager.Create<ILayoutExtension>();
51 context.TrackedFiles = this.TrackedFiles;
49 context.FileTransfers = this.FileTransfers; 52 context.FileTransfers = this.FileTransfers;
50 context.ContentFilePaths = this.ContentFilePaths;
51 context.ContentsFile = this.ContentsFile; 53 context.ContentsFile = this.ContentsFile;
52 context.OutputsFile = this.OutputsFile; 54 context.OutputsFile = this.OutputsFile;
53 context.BuiltOutputsFile = this.BuiltOutputsFile; 55 context.BuiltOutputsFile = this.BuiltOutputsFile;
@@ -71,24 +73,29 @@ namespace WixToolset.Core
71 var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.SuppressAclReset); 73 var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.SuppressAclReset);
72 command.Execute(); 74 command.Execute();
73 } 75 }
76
77 if (context.TrackedFiles != null)
78 {
79 this.CleanTempFiles(context.TrackedFiles);
80 }
74 } 81 }
75 finally 82 finally
76 { 83 {
77 if (!String.IsNullOrEmpty(context.ContentsFile) && context.ContentFilePaths != null) 84 if (context.TrackedFiles != null)
78 { 85 {
79 this.CreateContentsFile(context.ContentsFile, context.ContentFilePaths); 86 if (!String.IsNullOrEmpty(context.ContentsFile))
80 } 87 {
88 this.CreateContentsFile(context.ContentsFile, context.TrackedFiles);
89 }
81 90
82 if (context.FileTransfers != null)
83 {
84 if (!String.IsNullOrEmpty(context.OutputsFile)) 91 if (!String.IsNullOrEmpty(context.OutputsFile))
85 { 92 {
86 this.CreateOutputsFile(context.OutputsFile, context.FileTransfers); 93 this.CreateOutputsFile(context.OutputsFile, context.TrackedFiles);
87 } 94 }
88 95
89 if (!String.IsNullOrEmpty(context.BuiltOutputsFile)) 96 if (!String.IsNullOrEmpty(context.BuiltOutputsFile))
90 { 97 {
91 this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.FileTransfers); 98 this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.TrackedFiles);
92 } 99 }
93 } 100 }
94 } 101 }
@@ -105,16 +112,23 @@ namespace WixToolset.Core
105 /// </summary> 112 /// </summary>
106 /// <param name="path">Path to write file.</param> 113 /// <param name="path">Path to write file.</param>
107 /// <param name="contentFilePaths">Collection of paths to content files that will be written to file.</param> 114 /// <param name="contentFilePaths">Collection of paths to content files that will be written to file.</param>
108 private void CreateContentsFile(string path, IEnumerable<string> contentFilePaths) 115 private void CreateContentsFile(string path, IEnumerable<ITrackedFile> trackedFiles)
109 { 116 {
117 var uniqueInputFilePaths = new SortedSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Input).Select(t => t.Path), StringComparer.OrdinalIgnoreCase);
118
119 if (!uniqueInputFilePaths.Any())
120 {
121 return;
122 }
123
110 var directory = Path.GetDirectoryName(path); 124 var directory = Path.GetDirectoryName(path);
111 Directory.CreateDirectory(directory); 125 Directory.CreateDirectory(directory);
112 126
113 using (var contents = new StreamWriter(path, false)) 127 using (var contents = new StreamWriter(path, false))
114 { 128 {
115 foreach (string contentPath in contentFilePaths) 129 foreach (string inputPath in uniqueInputFilePaths)
116 { 130 {
117 contents.WriteLine(contentPath); 131 contents.WriteLine(inputPath);
118 } 132 }
119 } 133 }
120 } 134 }
@@ -124,22 +138,28 @@ namespace WixToolset.Core
124 /// </summary> 138 /// </summary>
125 /// <param name="path">Path to write file.</param> 139 /// <param name="path">Path to write file.</param>
126 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param> 140 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
127 private void CreateOutputsFile(string path, IEnumerable<IFileTransfer> fileTransfers) 141 private void CreateOutputsFile(string path, IEnumerable<ITrackedFile> trackedFiles)
128 { 142 {
143 var uniqueOutputPaths = new SortedSet<string>(trackedFiles.Where(t => t.Clean).Select(t => t.Path), StringComparer.OrdinalIgnoreCase);
144
145 if (!uniqueOutputPaths.Any())
146 {
147 return;
148 }
149
129 var directory = Path.GetDirectoryName(path); 150 var directory = Path.GetDirectoryName(path);
130 Directory.CreateDirectory(directory); 151 Directory.CreateDirectory(directory);
131 152
132 using (var outputs = new StreamWriter(path, false)) 153 using (var outputs = new StreamWriter(path, false))
133 { 154 {
134 foreach (var fileTransfer in fileTransfers) 155 //// Don't list files where the source is the same as the destination since
156 //// that might be the only place the file exists. The outputs file is often
157 //// used to delete stuff and losing the original source would be bad.
158 //var uniqueOutputPaths = new SortedSet<string>(fileTransfers.Where(ft => !ft.Redundant).Select(ft => ft.Destination), StringComparer.OrdinalIgnoreCase);
159
160 foreach (var outputPath in uniqueOutputPaths)
135 { 161 {
136 // Don't list files where the source is the same as the destination since 162 outputs.WriteLine(outputPath);
137 // that might be the only place the file exists. The outputs file is often
138 // used to delete stuff and losing the original source would be bad.
139 if (!fileTransfer.Redundant)
140 {
141 outputs.WriteLine(fileTransfer.Destination);
142 }
143 } 163 }
144 } 164 }
145 } 165 }
@@ -149,21 +169,83 @@ namespace WixToolset.Core
149 /// </summary> 169 /// </summary>
150 /// <param name="path">Path to write file.</param> 170 /// <param name="path">Path to write file.</param>
151 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param> 171 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
152 private void CreateBuiltOutputsFile(string path, IEnumerable<IFileTransfer> fileTransfers) 172 private void CreateBuiltOutputsFile(string path, IEnumerable<ITrackedFile> trackedFiles)
153 { 173 {
174 var uniqueBuiltPaths = new SortedSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Final).Select(t => t.Path), StringComparer.OrdinalIgnoreCase);
175
176 if (!uniqueBuiltPaths.Any())
177 {
178 return;
179 }
180
154 var directory = Path.GetDirectoryName(path); 181 var directory = Path.GetDirectoryName(path);
155 Directory.CreateDirectory(directory); 182 Directory.CreateDirectory(directory);
156 183
157 using (var outputs = new StreamWriter(path, false)) 184 using (var outputs = new StreamWriter(path, false))
158 { 185 {
159 foreach (var fileTransfer in fileTransfers) 186 foreach (var builtPath in uniqueBuiltPaths)
160 { 187 {
161 // Only write the built file transfers. Also, skip redundant 188 outputs.WriteLine(builtPath);
162 // files for the same reason spelled out in this.CreateOutputsFile(). 189 }
163 if (fileTransfer.Type == FileTransferType.Built && !fileTransfer.Redundant) 190 }
164 { 191 }
165 outputs.WriteLine(fileTransfer.Destination); 192
166 } 193 private void CleanTempFiles(IEnumerable<ITrackedFile> trackedFiles)
194 {
195 var uniqueTempPaths = new SortedSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Temporary).Select(t => t.Path), StringComparer.OrdinalIgnoreCase);
196
197 if (!uniqueTempPaths.Any())
198 {
199 return;
200 }
201
202 var uniqueFolders = new SortedSet<string>(StringComparer.OrdinalIgnoreCase)
203 {
204 this.IntermediateFolder
205 };
206
207 // Clean up temp files.
208 foreach (var tempPath in uniqueTempPaths)
209 {
210 try
211 {
212 this.SplitUniqueFolders(tempPath, uniqueFolders);
213
214 File.Delete(tempPath);
215 }
216 catch // delete is best effort.
217 {
218 }
219 }
220
221 // Clean up empty temp folders.
222 foreach (var folder in uniqueFolders.Reverse())
223 {
224 try
225 {
226 Directory.Delete(folder);
227 }
228 catch // delete is best effort.
229 {
230 }
231 }
232 }
233
234 private void SplitUniqueFolders(string tempPath, SortedSet<string> uniqueFolders)
235 {
236 if (tempPath.StartsWith(this.IntermediateFolder, StringComparison.OrdinalIgnoreCase))
237 {
238 var folder = Path.GetDirectoryName(tempPath).Substring(this.IntermediateFolder.Length);
239
240 var parts = folder.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
241
242 folder = this.IntermediateFolder;
243
244 foreach (var part in parts)
245 {
246 folder = Path.Combine(folder, part);
247
248 uniqueFolders.Add(folder);
167 } 249 }
168 } 250 }
169 } 251 }
diff --git a/src/WixToolset.Core/LayoutContext.cs b/src/WixToolset.Core/LayoutContext.cs
index 99ca611a..20d29b5f 100644
--- a/src/WixToolset.Core/LayoutContext.cs
+++ b/src/WixToolset.Core/LayoutContext.cs
@@ -22,7 +22,7 @@ namespace WixToolset.Core
22 22
23 public IEnumerable<IFileTransfer> FileTransfers { get; set; } 23 public IEnumerable<IFileTransfer> FileTransfers { get; set; }
24 24
25 public IEnumerable<string> ContentFilePaths { get; set; } 25 public IEnumerable<ITrackedFile> TrackedFiles { get; set; }
26 26
27 public string OutputPdbPath { get; set; } 27 public string OutputPdbPath { get; set; }
28 28
diff --git a/src/WixToolset.Core/TrackedFile.cs b/src/WixToolset.Core/TrackedFile.cs
new file mode 100644
index 00000000..312b09f4
--- /dev/null
+++ b/src/WixToolset.Core/TrackedFile.cs
@@ -0,0 +1,26 @@
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
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8 internal class TrackedFile : ITrackedFile
9 {
10 public TrackedFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers)
11 {
12 this.Path = path;
13 this.Type = type;
14 this.SourceLineNumbers = sourceLineNumbers;
15 this.Clean = (type == TrackedFileType.Intermediate || type == TrackedFileType.Final);
16 }
17
18 public bool Clean { get; set; }
19
20 public string Path { get; set; }
21
22 public SourceLineNumber SourceLineNumbers { get; set; }
23
24 public TrackedFileType Type { get; set; }
25 }
26}
diff --git a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
index cc631c22..e96c2ddd 100644
--- a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
@@ -56,7 +56,7 @@ namespace WixToolsetTest.CoreIntegration
56 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); 56 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")));
57 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\example.txt"))); 57 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\example.txt")));
58 58
59 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wir")); 59 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"extest.wir"));
60 var section = intermediate.Sections.Single(); 60 var section = intermediate.Sections.Single();
61 61
62 var wixFile = section.Tuples.OfType<WixFileTuple>().Single(); 62 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();
@@ -95,7 +95,7 @@ namespace WixToolsetTest.CoreIntegration
95 95
96 Assert.Equal(0, result); 96 Assert.Equal(0, result);
97 97
98 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wir")); 98 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"extest.wir"));
99 var section = intermediate.Sections.Single(); 99 var section = intermediate.Sections.Single();
100 100
101 var property = section.Tuples.OfType<PropertyTuple>().Where(p => p.Id.Id == "ExampleProperty").Single(); 101 var property = section.Tuples.OfType<PropertyTuple>().Where(p => p.Id.Id == "ExampleProperty").Single();
diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs
index 9d46ba98..126f334d 100644
--- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs
@@ -39,7 +39,7 @@ namespace WixToolsetTest.CoreIntegration
39 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); 39 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
40 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); 40 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt")));
41 41
42 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wir")); 42 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir"));
43 var section = intermediate.Sections.Single(); 43 var section = intermediate.Sections.Single();
44 44
45 var wixFile = section.Tuples.OfType<WixFileTuple>().Single(); 45 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();
@@ -74,7 +74,7 @@ namespace WixToolsetTest.CoreIntegration
74 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); 74 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab")));
75 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); 75 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
76 76
77 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); 77 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir"));
78 var section = intermediate.Sections.Single(); 78 var section = intermediate.Sections.Single();
79 79
80 var wixFile = section.Tuples.OfType<WixFileTuple>().Single(); 80 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();
@@ -264,7 +264,7 @@ namespace WixToolsetTest.CoreIntegration
264 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msm"))); 264 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msm")));
265 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); 265 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
266 266
267 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); 267 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir"));
268 var section = intermediate.Sections.Single(); 268 var section = intermediate.Sections.Single();
269 269
270 var wixFile = section.Tuples.OfType<WixFileTuple>().Single(); 270 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();
@@ -299,7 +299,7 @@ namespace WixToolsetTest.CoreIntegration
299 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); 299 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
300 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\test.txt"))); 300 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\test.txt")));
301 301
302 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); 302 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir"));
303 var section = intermediate.Sections.Single(); 303 var section = intermediate.Sections.Single();
304 304
305 var wixFile = section.Tuples.OfType<WixFileTuple>().Single(); 305 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();
@@ -399,7 +399,7 @@ namespace WixToolsetTest.CoreIntegration
399 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); 399 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
400 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); 400 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt")));
401 401
402 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wir")); 402 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir"));
403 var section = intermediate.Sections.Single(); 403 var section = intermediate.Sections.Single();
404 404
405 var wixFile = section.Tuples.OfType<WixFileTuple>().Single(); 405 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();
@@ -435,7 +435,7 @@ namespace WixToolsetTest.CoreIntegration
435 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); 435 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
436 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\AssemblyMsiPackage\candle.exe"))); 436 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\AssemblyMsiPackage\candle.exe")));
437 437
438 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wir")); 438 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"test.wir"));
439 var section = intermediate.Sections.Single(); 439 var section = intermediate.Sections.Single();
440 440
441 var wixFile = section.Tuples.OfType<WixFileTuple>().Single(); 441 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();
diff --git a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs
index 498b1492..a51d831f 100644
--- a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs
@@ -43,7 +43,7 @@ namespace WixToolsetTest.CoreIntegration
43 }, out var messagesBind); 43 }, out var messagesBind);
44 Assert.Equal(0, result); 44 Assert.Equal(0, result);
45 45
46 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wir")); 46 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"obj\test.wir"));
47 var section = intermediate.Sections.Single(); 47 var section = intermediate.Sections.Single();
48 48
49 var wixFile = section.Tuples.OfType<WixFileTuple>().Single(); 49 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();