aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Bind/Databases
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/Bind/Databases')
-rw-r--r--src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs314
-rw-r--r--src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs135
-rw-r--r--src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs176
-rw-r--r--src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs78
-rw-r--r--src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs91
-rw-r--r--src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs606
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs489
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs86
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs68
-rw-r--r--src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs225
-rw-r--r--src/WixToolset.Core/Bind/Databases/FileFacade.cs44
-rw-r--r--src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs148
-rw-r--r--src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs350
-rw-r--r--src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs115
-rw-r--r--src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs80
-rw-r--r--src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs532
16 files changed, 0 insertions, 3537 deletions
diff --git a/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs b/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs
deleted file mode 100644
index 5e2650e9..00000000
--- a/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs
+++ /dev/null
@@ -1,314 +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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11
12 /// <summary>
13 /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows.
14 /// </summary>
15 public class AssignMediaCommand : ICommand
16 {
17 public AssignMediaCommand()
18 {
19 this.CabinetNameTemplate = "Cab{0}.cab";
20 }
21
22 public Output Output { private get; set; }
23
24 public bool FilesCompressed { private get; set; }
25
26 public string CabinetNameTemplate { private get; set; }
27
28 public IEnumerable<FileFacade> FileFacades { private get; set; }
29
30 public TableDefinitionCollection TableDefinitions { private get; set; }
31
32 /// <summary>
33 /// Gets cabinets with their file rows.
34 /// </summary>
35 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; }
36
37 /// <summary>
38 /// Get media rows.
39 /// </summary>
40 public RowDictionary<MediaRow> MediaRows { get; private set; }
41
42 /// <summary>
43 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
44 /// This contains all the files when Package element is marked with compression=no
45 /// </summary>
46 public IEnumerable<FileFacade> UncompressedFileFacades { get; private set; }
47
48 public void Execute()
49 {
50 Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia = new Dictionary<MediaRow, List<FileFacade>>();
51
52 RowDictionary<MediaRow> mediaRows = new RowDictionary<MediaRow>();
53
54 List<FileFacade> uncompressedFiles = new List<FileFacade>();
55
56 MediaRow mergeModuleMediaRow = null;
57 Table mediaTable = this.Output.Tables["Media"];
58 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
59
60 // If both tables are authored, it is an error.
61 if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1))
62 {
63 throw new WixException(WixErrors.MediaTableCollision(null));
64 }
65
66 // When building merge module, all the files go to "#MergeModule.CABinet".
67 if (OutputType.Module == this.Output.Type)
68 {
69 Table mergeModuleMediaTable = new Table(null, this.TableDefinitions["Media"]);
70 mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null);
71 mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet";
72
73 filesByCabinetMedia.Add(mergeModuleMediaRow, new List<FileFacade>());
74 }
75
76 if (OutputType.Module == this.Output.Type || null == mediaTemplateTable)
77 {
78 this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
79 }
80 else
81 {
82 this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
83 }
84
85 this.FileFacadesByCabinetMedia = new Dictionary<MediaRow, IEnumerable<FileFacade>>();
86
87 foreach (var mediaRowWithFiles in filesByCabinetMedia)
88 {
89 this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value);
90 }
91
92 this.MediaRows = mediaRows;
93
94 this.UncompressedFileFacades = uncompressedFiles;
95 }
96
97 /// <summary>
98 /// Assign files to cabinets based on MediaTemplate authoring.
99 /// </summary>
100 /// <param name="fileFacades">FileRowCollection</param>
101 private void AutoAssignFiles(Table mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
102 {
103 const int MaxCabIndex = 999;
104
105 ulong currentPreCabSize = 0;
106 ulong maxPreCabSizeInBytes;
107 int maxPreCabSizeInMB = 0;
108 int currentCabIndex = 0;
109
110 MediaRow currentMediaRow = null;
111
112 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
113
114 // Auto assign files to cabinets based on maximum uncompressed media size
115 mediaTable.Rows.Clear();
116 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
117
118 if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate))
119 {
120 this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate;
121 }
122
123 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
124
125 try
126 {
127 // Override authored mums value if environment variable is authored.
128 if (!String.IsNullOrEmpty(mumsString))
129 {
130 maxPreCabSizeInMB = Int32.Parse(mumsString);
131 }
132 else
133 {
134 maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
135 }
136
137 maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024;
138 }
139 catch (FormatException)
140 {
141 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
142 }
143 catch (OverflowException)
144 {
145 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB));
146 }
147
148 foreach (FileFacade facade in this.FileFacades)
149 {
150 // When building a product, if the current file is not to be compressed or if
151 // the package set not to be compressed, don't cab it.
152 if (OutputType.Product == this.Output.Type &&
153 (YesNoType.No == facade.File.Compressed ||
154 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
155 {
156 uncompressedFiles.Add(facade);
157 continue;
158 }
159
160 if (currentCabIndex == MaxCabIndex)
161 {
162 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
163 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
164 facade.WixFile.DiskId = currentCabIndex;
165 cabinetFiles.Add(facade);
166 continue;
167 }
168
169 // Update current cab size.
170 currentPreCabSize += (ulong)facade.File.FileSize;
171
172 if (currentPreCabSize > maxPreCabSizeInBytes)
173 {
174 // Overflow due to current file
175 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
176 mediaRows.Add(currentMediaRow);
177 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
178
179 List<FileFacade> cabinetFileRows = filesByCabinetMedia[currentMediaRow];
180 facade.WixFile.DiskId = currentCabIndex;
181 cabinetFileRows.Add(facade);
182 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
183 currentPreCabSize = (ulong)facade.File.FileSize;
184 }
185 else
186 {
187 // File fits in the current cab.
188 if (currentMediaRow == null)
189 {
190 // Create new cab and MediaRow
191 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
192 mediaRows.Add(currentMediaRow);
193 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
194 }
195
196 // Associate current file with current cab.
197 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
198 facade.WixFile.DiskId = currentCabIndex;
199 cabinetFiles.Add(facade);
200 }
201 }
202
203 // If there are uncompressed files and no MediaRow, create a default one.
204 if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0)
205 {
206 MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null);
207 defaultMediaRow.DiskId = 1;
208 mediaRows.Add(defaultMediaRow);
209 }
210 }
211
212 /// <summary>
213 /// Assign files to cabinets based on Media authoring.
214 /// </summary>
215 /// <param name="mediaTable"></param>
216 /// <param name="mergeModuleMediaRow"></param>
217 /// <param name="fileFacades"></param>
218 private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
219 {
220 if (OutputType.Module != this.Output.Type)
221 {
222 if (null != mediaTable)
223 {
224 Dictionary<string, MediaRow> cabinetMediaRows = new Dictionary<string, MediaRow>(StringComparer.InvariantCultureIgnoreCase);
225 foreach (MediaRow mediaRow in mediaTable.Rows)
226 {
227 // If the Media row has a cabinet, make sure it is unique across all Media rows.
228 if (!String.IsNullOrEmpty(mediaRow.Cabinet))
229 {
230 MediaRow existingRow;
231 if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow))
232 {
233 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet));
234 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
235 }
236 else
237 {
238 cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow);
239 }
240 }
241
242 mediaRows.Add(mediaRow);
243 }
244 }
245
246 foreach (MediaRow mediaRow in mediaRows.Values)
247 {
248 if (null != mediaRow.Cabinet)
249 {
250 filesByCabinetMedia.Add(mediaRow, new List<FileFacade>());
251 }
252 }
253 }
254
255 foreach (FileFacade facade in fileFacades)
256 {
257 if (OutputType.Module == this.Output.Type)
258 {
259 filesByCabinetMedia[mergeModuleMediaRow].Add(facade);
260 }
261 else
262 {
263 MediaRow mediaRow;
264 if (!mediaRows.TryGetValue(facade.WixFile.DiskId.ToString(CultureInfo.InvariantCulture), out mediaRow))
265 {
266 Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId));
267 continue;
268 }
269
270 // When building a product, if the current file is not to be compressed or if
271 // the package set not to be compressed, don't cab it.
272 if (OutputType.Product == this.Output.Type &&
273 (YesNoType.No == facade.File.Compressed ||
274 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
275 {
276 uncompressedFiles.Add(facade);
277 }
278 else // file is marked compressed.
279 {
280 List<FileFacade> cabinetFiles;
281 if (filesByCabinetMedia.TryGetValue(mediaRow, out cabinetFiles))
282 {
283 cabinetFiles.Add(facade);
284 }
285 else
286 {
287 Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId));
288 }
289 }
290 }
291 }
292 }
293
294 /// <summary>
295 /// Adds a row to the media table with cab name template filled in.
296 /// </summary>
297 /// <param name="mediaTable"></param>
298 /// <param name="cabIndex"></param>
299 /// <returns></returns>
300 private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex)
301 {
302 MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
303 currentMediaRow.DiskId = cabIndex;
304 currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex);
305
306 Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]);
307 WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
308 row.DiskId = cabIndex;
309 row.CompressionLevel = mediaTemplateRow.CompressionLevel;
310
311 return currentMediaRow;
312 }
313 }
314}
diff --git a/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs b/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs
deleted file mode 100644
index 95bd4cf0..00000000
--- a/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs
+++ /dev/null
@@ -1,135 +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.Bind.Databases
4{
5 using System;
6 using System.Globalization;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Binds the summary information table of a database.
11 /// </summary>
12 internal class BindSummaryInfoCommand : ICommand
13 {
14 /// <summary>
15 /// The output to bind.
16 /// </summary>
17 public Output Output { private get; set; }
18
19 /// <summary>
20 /// Returns a flag indicating if files are compressed by default.
21 /// </summary>
22 public bool Compressed { get; private set; }
23
24 /// <summary>
25 /// Returns a flag indicating if uncompressed files use long filenames.
26 /// </summary>
27 public bool LongNames { get; private set; }
28
29 public int InstallerVersion { get; private set; }
30
31 /// <summary>
32 /// Modularization guid, or null if the output is not a module.
33 /// </summary>
34 public string ModularizationGuid { get; private set; }
35
36 public void Execute()
37 {
38 this.Compressed = false;
39 this.LongNames = false;
40 this.InstallerVersion = 0;
41 this.ModularizationGuid = null;
42
43 Table summaryInformationTable = this.Output.Tables["_SummaryInformation"];
44
45 if (null != summaryInformationTable)
46 {
47 bool foundCreateDataTime = false;
48 bool foundLastSaveDataTime = false;
49 bool foundCreatingApplication = false;
50 string now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
51
52 foreach (Row summaryInformationRow in summaryInformationTable.Rows)
53 {
54 switch (summaryInformationRow.FieldAsInteger(0))
55 {
56 case 1: // PID_CODEPAGE
57 // make sure the code page is an int and not a web name or null
58 string codepage = summaryInformationRow.FieldAsString(1);
59
60 if (null == codepage)
61 {
62 codepage = "0";
63 }
64 else
65 {
66 summaryInformationRow[1] = Common.GetValidCodePage(codepage, false, false, summaryInformationRow.SourceLineNumbers).ToString(CultureInfo.InvariantCulture);
67 }
68 break;
69 case 9: // PID_REVNUMBER
70 string packageCode = (string)summaryInformationRow[1];
71
72 if (OutputType.Module == this.Output.Type)
73 {
74 this.ModularizationGuid = packageCode.Substring(1, 36).Replace('-', '_');
75 }
76 else if ("*" == packageCode)
77 {
78 // set the revision number (package/patch code) if it should be automatically generated
79 summaryInformationRow[1] = Common.GenerateGuid();
80 }
81 break;
82 case 12: // PID_CREATE_DTM
83 foundCreateDataTime = true;
84 break;
85 case 13: // PID_LASTSAVE_DTM
86 foundLastSaveDataTime = true;
87 break;
88 case 14:
89 this.InstallerVersion = summaryInformationRow.FieldAsInteger(1);
90 break;
91 case 15: // PID_WORDCOUNT
92 if (OutputType.Patch == this.Output.Type)
93 {
94 this.LongNames = true;
95 this.Compressed = true;
96 }
97 else
98 {
99 this.LongNames = (0 == (summaryInformationRow.FieldAsInteger(1) & 1));
100 this.Compressed = (2 == (summaryInformationRow.FieldAsInteger(1) & 2));
101 }
102 break;
103 case 18: // PID_APPNAME
104 foundCreatingApplication = true;
105 break;
106 }
107 }
108
109 // add a summary information row for the create time/date property if its not already set
110 if (!foundCreateDataTime)
111 {
112 Row createTimeDateRow = summaryInformationTable.CreateRow(null);
113 createTimeDateRow[0] = 12;
114 createTimeDateRow[1] = now;
115 }
116
117 // add a summary information row for the last save time/date property if its not already set
118 if (!foundLastSaveDataTime)
119 {
120 Row lastSaveTimeDateRow = summaryInformationTable.CreateRow(null);
121 lastSaveTimeDateRow[0] = 13;
122 lastSaveTimeDateRow[1] = now;
123 }
124
125 // add a summary information row for the creating application property if its not already set
126 if (!foundCreatingApplication)
127 {
128 Row creatingApplicationRow = summaryInformationTable.CreateRow(null);
129 creatingApplicationRow[0] = 18;
130 creatingApplicationRow[1] = String.Format(CultureInfo.InvariantCulture, AppCommon.GetCreatingApplicationString());
131 }
132 }
133 }
134 }
135}
diff --git a/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs b/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs
deleted file mode 100644
index 2de6ec25..00000000
--- a/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs
+++ /dev/null
@@ -1,176 +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.Bind.Databases
4{
5 using System;
6 using System.Collections;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using WixToolset.Cab;
11 using WixToolset.Data;
12 using WixToolset.Data.Rows;
13
14 /// <summary>
15 /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple
16 /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished.
17 /// </summary>
18 internal sealed class CabinetBuilder
19 {
20 private Queue cabinetWorkItems;
21 private object lockObject;
22 private int threadCount;
23
24 // Address of Binder's callback function for Cabinet Splitting
25 private IntPtr newCabNamesCallBackAddress;
26
27 public int MaximumCabinetSizeForLargeFileSplitting { get; set; }
28 public int MaximumUncompressedMediaSize { get; set; }
29
30 /// <summary>
31 /// Instantiate a new CabinetBuilder.
32 /// </summary>
33 /// <param name="threadCount">number of threads to use</param>
34 /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param>
35 public CabinetBuilder(int threadCount, IntPtr newCabNamesCallBackAddress)
36 {
37 if (0 >= threadCount)
38 {
39 throw new ArgumentOutOfRangeException("threadCount");
40 }
41
42 this.cabinetWorkItems = new Queue();
43 this.lockObject = new object();
44
45 this.threadCount = threadCount;
46
47 // Set Address of Binder's callback function for Cabinet Splitting
48 this.newCabNamesCallBackAddress = newCabNamesCallBackAddress;
49 }
50
51 /// <summary>
52 /// Enqueues a CabinetWorkItem to the queue.
53 /// </summary>
54 /// <param name="cabinetWorkItem">cabinet work item</param>
55 public void Enqueue(CabinetWorkItem cabinetWorkItem)
56 {
57 this.cabinetWorkItems.Enqueue(cabinetWorkItem);
58 }
59
60 /// <summary>
61 /// Create the queued cabinets.
62 /// </summary>
63 /// <returns>error message number (zero if no error)</returns>
64 public void CreateQueuedCabinets()
65 {
66 // don't create more threads than the number of cabinets to build
67 if (this.cabinetWorkItems.Count < this.threadCount)
68 {
69 this.threadCount = this.cabinetWorkItems.Count;
70 }
71
72 if (0 < this.threadCount)
73 {
74 Thread[] threads = new Thread[this.threadCount];
75
76 for (int i = 0; i < threads.Length; i++)
77 {
78 threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems));
79 threads[i].Start();
80 }
81
82 // wait for all threads to finish
83 foreach (Thread thread in threads)
84 {
85 thread.Join();
86 }
87 }
88 }
89
90 /// <summary>
91 /// This function gets called by multiple threads to do actual work.
92 /// It takes one work item at a time and calls this.CreateCabinet().
93 /// It does not return until cabinetWorkItems queue is empty
94 /// </summary>
95 private void ProcessWorkItems()
96 {
97 try
98 {
99 while (true)
100 {
101 CabinetWorkItem cabinetWorkItem;
102
103 lock (this.cabinetWorkItems)
104 {
105 // check if there are any more cabinets to create
106 if (0 == this.cabinetWorkItems.Count)
107 {
108 break;
109 }
110
111 cabinetWorkItem = (CabinetWorkItem)this.cabinetWorkItems.Dequeue();
112 }
113
114 // create a cabinet
115 this.CreateCabinet(cabinetWorkItem);
116 }
117 }
118 catch (WixException we)
119 {
120 Messaging.Instance.OnMessage(we.Error);
121 }
122 catch (Exception e)
123 {
124 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace));
125 }
126 }
127
128 /// <summary>
129 /// Creates a cabinet using the wixcab.dll interop layer.
130 /// </summary>
131 /// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param>
132 private void CreateCabinet(CabinetWorkItem cabinetWorkItem)
133 {
134 Messaging.Instance.OnMessage(WixVerboses.CreateCabinet(cabinetWorkItem.CabinetFile));
135
136 int maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting
137 ulong maxPreCompressedSizeInBytes = 0;
138
139 if (MaximumCabinetSizeForLargeFileSplitting != 0)
140 {
141 // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize
142 // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file
143 if (1 == cabinetWorkItem.FileFacades.Count())
144 {
145 // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs
146 // Get the Value for Max Uncompressed Media Size
147 maxPreCompressedSizeInBytes = (ulong)MaximumUncompressedMediaSize * 1024 * 1024;
148
149 foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row
150 {
151 if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes)
152 {
153 // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting
154 maxCabinetSize = MaximumCabinetSizeForLargeFileSplitting;
155 }
156 }
157 }
158 }
159
160 // create the cabinet file
161 string cabinetFileName = Path.GetFileName(cabinetWorkItem.CabinetFile);
162 string cabinetDirectory = Path.GetDirectoryName(cabinetWorkItem.CabinetFile);
163
164 using (WixCreateCab cab = new WixCreateCab(cabinetFileName, cabinetDirectory, cabinetWorkItem.FileFacades.Count(), maxCabinetSize, cabinetWorkItem.MaxThreshold, cabinetWorkItem.CompressionLevel))
165 {
166 foreach (FileFacade facade in cabinetWorkItem.FileFacades)
167 {
168 cab.AddFile(facade);
169 }
170
171 cab.Complete(newCabNamesCallBackAddress);
172 }
173 }
174 }
175}
176
diff --git a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs b/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs
deleted file mode 100644
index 20241bc9..00000000
--- a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs
+++ /dev/null
@@ -1,78 +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.Bind.Databases
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Data.Rows;
8
9 /// <summary>
10 /// A cabinet builder work item.
11 /// </summary>
12 internal sealed class CabinetWorkItem
13 {
14 private string cabinetFile;
15 private CompressionLevel compressionLevel;
16 //private BinderFileManager binderFileManager;
17 private int maxThreshold;
18
19 /// <summary>
20 /// Instantiate a new CabinetWorkItem.
21 /// </summary>
22 /// <param name="fileFacades">The collection of files in this cabinet.</param>
23 /// <param name="cabinetFile">The cabinet file.</param>
24 /// <param name="maxThreshold">Maximum threshold for each cabinet.</param>
25 /// <param name="compressionLevel">The compression level of the cabinet.</param>
26 /// <param name="binderFileManager">The binder file manager.</param>
27 public CabinetWorkItem(IEnumerable<FileFacade> fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel /*, BinderFileManager binderFileManager*/)
28 {
29 this.cabinetFile = cabinetFile;
30 this.compressionLevel = compressionLevel;
31 this.FileFacades = fileFacades;
32 //this.binderFileManager = binderFileManager;
33 this.maxThreshold = maxThreshold;
34 }
35
36 /// <summary>
37 /// Gets the cabinet file.
38 /// </summary>
39 /// <value>The cabinet file.</value>
40 public string CabinetFile
41 {
42 get { return this.cabinetFile; }
43 }
44
45 /// <summary>
46 /// Gets the compression level of the cabinet.
47 /// </summary>
48 /// <value>The compression level of the cabinet.</value>
49 public CompressionLevel CompressionLevel
50 {
51 get { return this.compressionLevel; }
52 }
53
54 /// <summary>
55 /// Gets the collection of files in this cabinet.
56 /// </summary>
57 /// <value>The collection of files in this cabinet.</value>
58 public IEnumerable<FileFacade> FileFacades { get; private set; }
59
60 /// <summary>
61 /// Gets the binder file manager.
62 /// </summary>
63 /// <value>The binder file manager.</value>
64 //public BinderFileManager BinderFileManager
65 //{
66 // get { return this.binderFileManager; }
67 //}
68
69 /// <summary>
70 /// Gets the max threshold.
71 /// </summary>
72 /// <value>The maximum threshold for a folder in a cabinet.</value>
73 public int MaxThreshold
74 {
75 get { return this.maxThreshold; }
76 }
77 }
78}
diff --git a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs b/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs
deleted file mode 100644
index 7cb18e0f..00000000
--- a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs
+++ /dev/null
@@ -1,91 +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.Bind.Databases
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8 using WixToolset.MergeMod;
9
10 /// <summary>
11 /// Callback object for configurable merge modules.
12 /// </summary>
13 internal sealed class ConfigurationCallback : IMsmConfigureModule
14 {
15 private const int SOk = 0x0;
16 private const int SFalse = 0x1;
17 private Hashtable configurationData;
18
19 /// <summary>
20 /// Creates a ConfigurationCallback object.
21 /// </summary>
22 /// <param name="configData">String to break up into name/value pairs.</param>
23 public ConfigurationCallback(string configData)
24 {
25 if (String.IsNullOrEmpty(configData))
26 {
27 throw new ArgumentNullException("configData");
28 }
29
30 string[] pairs = configData.Split(',');
31 this.configurationData = new Hashtable(pairs.Length);
32 for (int i = 0; i < pairs.Length; ++i)
33 {
34 string[] nameVal = pairs[i].Split('=');
35 string name = nameVal[0];
36 string value = nameVal[1];
37
38 name = name.Replace("%2C", ",");
39 name = name.Replace("%3D", "=");
40 name = name.Replace("%25", "%");
41
42 value = value.Replace("%2C", ",");
43 value = value.Replace("%3D", "=");
44 value = value.Replace("%25", "%");
45
46 this.configurationData[name] = value;
47 }
48 }
49
50 /// <summary>
51 /// Returns text data based on name.
52 /// </summary>
53 /// <param name="name">Name of value to return.</param>
54 /// <param name="configData">Out param to put configuration data into.</param>
55 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
56 public int ProvideTextData(string name, out string configData)
57 {
58 if (this.configurationData.Contains(name))
59 {
60 configData = (string)this.configurationData[name];
61 return SOk;
62 }
63 else
64 {
65 configData = null;
66 return SFalse;
67 }
68 }
69
70 /// <summary>
71 /// Returns integer data based on name.
72 /// </summary>
73 /// <param name="name">Name of value to return.</param>
74 /// <param name="configData">Out param to put configuration data into.</param>
75 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
76 public int ProvideIntegerData(string name, out int configData)
77 {
78 if (this.configurationData.Contains(name))
79 {
80 string val = (string)this.configurationData[name];
81 configData = Convert.ToInt32(val, CultureInfo.InvariantCulture);
82 return SOk;
83 }
84 else
85 {
86 configData = 0;
87 return SFalse;
88 }
89 }
90 }
91}
diff --git a/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs b/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs
deleted file mode 100644
index af1ab3b0..00000000
--- a/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs
+++ /dev/null
@@ -1,606 +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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using WixToolset.Data;
9 using WixToolset.Data.Rows;
10 using WixToolset.Extensibility;
11 using WixToolset.Core.Native;
12
13 internal class CopyTransformDataCommand : ICommand
14 {
15 public bool CopyOutFileRows { private get; set; }
16
17 public BinderFileManagerCore FileManagerCore { private get; set; }
18
19 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
20
21 public Output Output { private get; set; }
22
23 public TableDefinitionCollection TableDefinitions { private get; set; }
24
25 public IEnumerable<FileFacade> FileFacades { get; private set; }
26
27 public void Execute()
28 {
29 Debug.Assert(OutputType.Patch != this.Output.Type);
30
31 List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null;
32
33#if false // TODO: Fix this patching related code to work correctly with FileFacades.
34 bool copyToPatch = (allFileRows != null);
35 bool copyFromPatch = !copyToPatch;
36
37 RowDictionary<MediaRow> patchMediaRows = new RowDictionary<MediaRow>();
38
39 Dictionary<int, RowDictionary<WixFileRow>> patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>();
40
41 Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
42 Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]);
43
44 if (copyFromPatch)
45 {
46 // index patch files by diskId+fileId
47 foreach (WixFileRow patchFileRow in patchFileTable.Rows)
48 {
49 int diskId = patchFileRow.DiskId;
50 RowDictionary<WixFileRow> mediaFileRows;
51 if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
52 {
53 mediaFileRows = new RowDictionary<WixFileRow>();
54 patchMediaFileRows.Add(diskId, mediaFileRows);
55 }
56
57 mediaFileRows.Add(patchFileRow);
58 }
59
60 Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]);
61 patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable);
62 }
63
64 // index paired transforms
65 Dictionary<string, Output> pairedTransforms = new Dictionary<string, Output>();
66 foreach (SubStorage substorage in this.Output.SubStorages)
67 {
68 if (substorage.Name.StartsWith("#"))
69 {
70 pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data);
71 }
72 }
73
74 try
75 {
76 // copy File bind data into substorages
77 foreach (SubStorage substorage in this.Output.SubStorages)
78 {
79 if (substorage.Name.StartsWith("#"))
80 {
81 // no changes necessary for paired transforms
82 continue;
83 }
84
85 Output mainTransform = substorage.Data;
86 Table mainWixFileTable = mainTransform.Tables["WixFile"];
87 Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"];
88
89 this.FileManagerCore.ActiveSubStorage = substorage;
90
91 RowDictionary<WixFileRow> mainWixFiles = new RowDictionary<WixFileRow>(mainWixFileTable);
92 RowDictionary<Row> mainMsiFileHashIndex = new RowDictionary<Row>();
93
94 Table mainFileTable = mainTransform.Tables["File"];
95 Output pairedTransform = (Output)pairedTransforms[substorage.Name];
96
97 // copy Media.LastSequence and index the MsiFileHash table if it exists.
98 if (copyFromPatch)
99 {
100 Table pairedMediaTable = pairedTransform.Tables["Media"];
101 foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
102 {
103 MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
104 pairedMediaRow.Fields[1] = patchMediaRow.Fields[1];
105 }
106
107 if (null != mainMsiFileHashTable)
108 {
109 mainMsiFileHashIndex = new RowDictionary<Row>(mainMsiFileHashTable);
110 }
111
112 // Validate file row changes for keypath-related issues
113 this.ValidateFileRowChanges(mainTransform);
114 }
115
116 // Index File table of pairedTransform
117 Table pairedFileTable = pairedTransform.Tables["File"];
118 RowDictionary<FileRow> pairedFileRows = new RowDictionary<FileRow>(pairedFileTable);
119
120 if (null != mainFileTable)
121 {
122 if (copyFromPatch)
123 {
124 // Remove the MsiFileHash table because it will be updated later with the final file hash for each file
125 mainTransform.Tables.Remove("MsiFileHash");
126 }
127
128 foreach (FileRow mainFileRow in mainFileTable.Rows)
129 {
130 if (RowOperation.Delete == mainFileRow.Operation)
131 {
132 continue;
133 }
134 else if (RowOperation.None == mainFileRow.Operation && !copyToPatch)
135 {
136 continue;
137 }
138
139 WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File);
140
141 if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes.
142 {
143 ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6];
144 FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File);
145
146 // If the file is new, we always need to add it to the patch.
147 if (mainFileRow.Operation != RowOperation.Add)
148 {
149 // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare.
150 if (null == objectField.PreviousData)
151 {
152 if (mainFileRow.Operation == RowOperation.None)
153 {
154 continue;
155 }
156 }
157 else
158 {
159 // TODO: should this entire condition be placed in the binder file manager?
160 if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&
161 !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString()))
162 {
163 // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
164 mainFileRow.Operation = RowOperation.Modify;
165 if (null != pairedFileRow)
166 {
167 // Always patch-added, but never non-compressed.
168 pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
169 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
170 pairedFileRow.Fields[6].Modified = true;
171 pairedFileRow.Operation = RowOperation.Modify;
172 }
173 }
174 else
175 {
176 // The File is same. We need mark all the attributes as unchanged.
177 mainFileRow.Operation = RowOperation.None;
178 foreach (Field field in mainFileRow.Fields)
179 {
180 field.Modified = false;
181 }
182
183 if (null != pairedFileRow)
184 {
185 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded;
186 pairedFileRow.Fields[6].Modified = false;
187 pairedFileRow.Operation = RowOperation.None;
188 }
189 continue;
190 }
191 }
192 }
193 else if (null != pairedFileRow) // RowOperation.Add
194 {
195 // Always patch-added, but never non-compressed.
196 pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
197 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
198 pairedFileRow.Fields[6].Modified = true;
199 pairedFileRow.Operation = RowOperation.Add;
200 }
201 }
202
203 // index patch files by diskId+fileId
204 int diskId = mainWixFileRow.DiskId;
205
206 RowDictionary<WixFileRow> mediaFileRows;
207 if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
208 {
209 mediaFileRows = new RowDictionary<WixFileRow>();
210 patchMediaFileRows.Add(diskId, mediaFileRows);
211 }
212
213 string fileId = mainFileRow.File;
214 WixFileRow patchFileRow = mediaFileRows.Get(fileId);
215 if (copyToPatch)
216 {
217 if (null == patchFileRow)
218 {
219 FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
220 patchActualFileRow.CopyFrom(mainFileRow);
221
222 patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
223 patchFileRow.CopyFrom(mainWixFileRow);
224
225 mediaFileRows.Add(patchFileRow);
226
227 allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right?
228 }
229 else
230 {
231 // TODO: confirm the rest of data is identical?
232
233 // make sure Source is same. Otherwise we are silently ignoring a file.
234 if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase))
235 {
236 Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source));
237 }
238
239 // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
240 patchFileRow.AppendPreviousDataFrom(mainWixFileRow);
241 }
242 }
243 else
244 {
245 // copy data from the patch back to the transform
246 if (null != patchFileRow)
247 {
248 FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId);
249 for (int i = 0; i < patchFileRow.Fields.Length; i++)
250 {
251 string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString();
252 string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString();
253
254 if (1 == i)
255 {
256 // File.Component_ changes should not come from the shared file rows
257 // that contain the file information as each individual transform might
258 // have different changes (or no changes at all).
259 }
260 // File.Attributes should not changed for binary deltas
261 else if (6 == i)
262 {
263 if (null != patchFileRow.Patch)
264 {
265 // File.Attribute should not change for binary deltas
266 pairedFileRow.Attributes = mainFileRow.Attributes;
267 mainFileRow.Fields[i].Modified = false;
268 }
269 }
270 // File.Sequence is updated in pairedTransform, not mainTransform
271 else if (7 == i)
272 {
273 // file sequence is updated in Patch table instead of File table for delta patches
274 if (null != patchFileRow.Patch)
275 {
276 pairedFileRow.Fields[i].Modified = false;
277 }
278 else
279 {
280 pairedFileRow[i] = patchFileRow[i];
281 pairedFileRow.Fields[i].Modified = true;
282 }
283 mainFileRow.Fields[i].Modified = false;
284 }
285 else if (patchValue != mainValue)
286 {
287 mainFileRow[i] = patchFileRow[i];
288 mainFileRow.Fields[i].Modified = true;
289 if (mainFileRow.Operation == RowOperation.None)
290 {
291 mainFileRow.Operation = RowOperation.Modify;
292 }
293 }
294 }
295
296 // copy MsiFileHash row for this File
297 Row patchHashRow;
298 if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow))
299 {
300 patchHashRow = patchFileRow.Hash;
301 }
302
303 if (null != patchHashRow)
304 {
305 Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
306 Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
307 for (int i = 0; i < patchHashRow.Fields.Length; i++)
308 {
309 mainHashRow[i] = patchHashRow[i];
310 if (i > 1)
311 {
312 // assume all hash fields have been modified
313 mainHashRow.Fields[i].Modified = true;
314 }
315 }
316
317 // assume the MsiFileHash operation follows the File one
318 mainHashRow.Operation = mainFileRow.Operation;
319 }
320
321 // copy MsiAssemblyName rows for this File
322 List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
323 if (null != patchAssemblyNameRows)
324 {
325 Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
326 foreach (Row patchAssemblyNameRow in patchAssemblyNameRows)
327 {
328 // Copy if there isn't an identical modified/added row already in the transform.
329 bool foundMatchingModifiedRow = false;
330 foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows)
331 {
332 if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
333 {
334 foundMatchingModifiedRow = true;
335 break;
336 }
337 }
338
339 if (!foundMatchingModifiedRow)
340 {
341 Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
342 for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
343 {
344 mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
345 }
346
347 // assume value field has been modified
348 mainAssemblyNameRow.Fields[2].Modified = true;
349 mainAssemblyNameRow.Operation = mainFileRow.Operation;
350 }
351 }
352 }
353
354 // Add patch header for this file
355 if (null != patchFileRow.Patch)
356 {
357 // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
358 AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
359 AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
360
361 // Add to Patch table
362 Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
363 if (0 == patchTable.Rows.Count)
364 {
365 patchTable.Operation = TableOperation.Add;
366 }
367
368 Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
369 patchRow[0] = patchFileRow.File;
370 patchRow[1] = patchFileRow.Sequence;
371
372 FileInfo patchFile = new FileInfo(patchFileRow.Source);
373 patchRow[2] = (int)patchFile.Length;
374 patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
375
376 string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
377 if (MsiInterop.MsiMaxStreamNameLength < streamName.Length)
378 {
379 streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
380 Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
381 if (0 == patchHeadersTable.Rows.Count)
382 {
383 patchHeadersTable.Operation = TableOperation.Add;
384 }
385 Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
386 patchHeadersRow[0] = streamName;
387 patchHeadersRow[1] = patchFileRow.Patch;
388 patchRow[5] = streamName;
389 patchHeadersRow.Operation = RowOperation.Add;
390 }
391 else
392 {
393 patchRow[4] = patchFileRow.Patch;
394 }
395 patchRow.Operation = RowOperation.Add;
396 }
397 }
398 else
399 {
400 // TODO: throw because all transform rows should have made it into the patch
401 }
402 }
403 }
404 }
405
406 if (copyFromPatch)
407 {
408 this.Output.Tables.Remove("Media");
409 this.Output.Tables.Remove("File");
410 this.Output.Tables.Remove("MsiFileHash");
411 this.Output.Tables.Remove("MsiAssemblyName");
412 }
413 }
414 }
415 finally
416 {
417 this.FileManagerCore.ActiveSubStorage = null;
418 }
419#endif
420 this.FileFacades = allFileRows;
421 }
422
423 /// <summary>
424 /// Adds the PatchFiles action to the sequence table if it does not already exist.
425 /// </summary>
426 /// <param name="table">The sequence table to check or modify.</param>
427 /// <param name="mainTransform">The primary authoring transform.</param>
428 /// <param name="pairedTransform">The secondary patch transform.</param>
429 /// <param name="mainFileRow">The file row that contains information about the patched file.</param>
430 private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow)
431 {
432 // Find/add PatchFiles action (also determine sequence for it).
433 // Search mainTransform first, then pairedTransform (pairedTransform overrides).
434 bool hasPatchFilesAction = false;
435 int seqInstallFiles = 0;
436 int seqDuplicateFiles = 0;
437 string tableName = table.ToString();
438
439 TestSequenceTableForPatchFilesAction(
440 mainTransform.Tables[tableName],
441 ref hasPatchFilesAction,
442 ref seqInstallFiles,
443 ref seqDuplicateFiles);
444 TestSequenceTableForPatchFilesAction(
445 pairedTransform.Tables[tableName],
446 ref hasPatchFilesAction,
447 ref seqInstallFiles,
448 ref seqDuplicateFiles);
449 if (!hasPatchFilesAction)
450 {
451 Table iesTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]);
452 if (0 == iesTable.Rows.Count)
453 {
454 iesTable.Operation = TableOperation.Add;
455 }
456
457 Row patchAction = iesTable.CreateRow(null);
458 WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"];
459 int sequence = wixPatchAction.Sequence;
460 // Test for default sequence value's appropriateness
461 if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence))
462 {
463 if (0 != seqDuplicateFiles)
464 {
465 if (seqDuplicateFiles < seqInstallFiles)
466 {
467 throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action));
468 }
469 else
470 {
471 sequence = (seqDuplicateFiles + seqInstallFiles) / 2;
472 if (seqInstallFiles == sequence || seqDuplicateFiles == sequence)
473 {
474 throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action));
475 }
476 }
477 }
478 else
479 {
480 sequence = seqInstallFiles + 1;
481 }
482 }
483 patchAction[0] = wixPatchAction.Action;
484 patchAction[1] = wixPatchAction.Condition;
485 patchAction[2] = sequence;
486 patchAction.Operation = RowOperation.Add;
487 }
488 }
489
490 /// <summary>
491 /// Tests sequence table for PatchFiles and associated actions
492 /// </summary>
493 /// <param name="iesTable">The table to test.</param>
494 /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param>
495 /// <param name="seqInstallFiles">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param>
496 /// <param name="seqDuplicateFiles">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param>
497 private static void TestSequenceTableForPatchFilesAction(Table iesTable, ref bool hasPatchFilesAction, ref int seqInstallFiles, ref int seqDuplicateFiles)
498 {
499 if (null != iesTable)
500 {
501 foreach (Row iesRow in iesTable.Rows)
502 {
503 if (String.Equals("PatchFiles", (string)iesRow[0], StringComparison.Ordinal))
504 {
505 hasPatchFilesAction = true;
506 }
507 if (String.Equals("InstallFiles", (string)iesRow[0], StringComparison.Ordinal))
508 {
509 seqInstallFiles = (int)iesRow.Fields[2].Data;
510 }
511 if (String.Equals("DuplicateFiles", (string)iesRow[0], StringComparison.Ordinal))
512 {
513 seqDuplicateFiles = (int)iesRow.Fields[2].Data;
514 }
515 }
516 }
517 }
518
519 /// <summary>
520 /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component.
521 /// </summary>
522 /// <param name="output">The output to validate.</param>
523 private void ValidateFileRowChanges(Output transform)
524 {
525 Table componentTable = transform.Tables["Component"];
526 Table fileTable = transform.Tables["File"];
527
528 // There's no sense validating keypaths if the transform has no component or file table
529 if (componentTable == null || fileTable == null)
530 {
531 return;
532 }
533
534 Dictionary<string, string> componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
535
536 // Index the Component table for non-directory & non-registry key paths.
537 foreach (Row row in componentTable.Rows)
538 {
539 if (null != row.Fields[5].Data &&
540 0 != ((int)row.Fields[3].Data & MsiInterop.MsidbComponentAttributesRegistryKeyPath))
541 {
542 componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString());
543 }
544 }
545
546 Dictionary<string, string> componentWithChangedKeyPath = new Dictionary<string, string>();
547 Dictionary<string, string> componentWithNonKeyPathChanged = new Dictionary<string, string>();
548 // Verify changes in the file table, now that file diffing has occurred
549 foreach (FileRow row in fileTable.Rows)
550 {
551 string fileId = row.Fields[0].Data.ToString();
552 string componentId = row.Fields[1].Data.ToString();
553
554 if (RowOperation.Modify != row.Operation)
555 {
556 continue;
557 }
558
559 // If this file is the keypath of a component
560 if (componentKeyPath.ContainsValue(fileId))
561 {
562 if (!componentWithChangedKeyPath.ContainsKey(componentId))
563 {
564 componentWithChangedKeyPath.Add(componentId, fileId);
565 }
566 }
567 else
568 {
569 if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
570 {
571 componentWithNonKeyPathChanged.Add(componentId, fileId);
572 }
573 }
574 }
575
576 foreach (KeyValuePair<string, string> componentFile in componentWithNonKeyPathChanged)
577 {
578 // Make sure all changes to non keypath files also had a change in the keypath.
579 if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key))
580 {
581 Messaging.Instance.OnMessage(WixWarnings.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key]));
582 }
583 }
584 }
585
586 private bool CompareFiles(string targetFile, string updatedFile)
587 {
588 bool? compared = null;
589 foreach (IBinderFileManager fileManager in this.FileManagers)
590 {
591 compared = fileManager.CompareFiles(targetFile, updatedFile);
592 if (compared.HasValue)
593 {
594 break;
595 }
596 }
597
598 if (!compared.HasValue)
599 {
600 throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
601 }
602
603 return compared.Value;
604 }
605 }
606}
diff --git a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
deleted file mode 100644
index 35c8abb4..00000000
--- a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
+++ /dev/null
@@ -1,489 +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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using System.Threading;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using WixToolset.Extensibility;
15
16 /// <summary>
17 /// Creates cabinet files.
18 /// </summary>
19 internal class CreateCabinetsCommand : ICommand
20 {
21 private List<FileTransfer> fileTransfers;
22
23 private FileSplitCabNamesCallback newCabNamesCallBack;
24
25 private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence
26
27 public CreateCabinetsCommand()
28 {
29 this.fileTransfers = new List<FileTransfer>();
30
31 this.newCabNamesCallBack = NewCabNamesCallBack;
32 }
33
34 /// <summary>
35 /// Sets the number of threads to use for cabinet creation.
36 /// </summary>
37 public int CabbingThreadCount { private get; set; }
38
39 public string TempFilesLocation { private get; set; }
40
41 /// <summary>
42 /// Sets the default compression level to use for cabinets
43 /// that don't have their compression level explicitly set.
44 /// </summary>
45 public CompressionLevel DefaultCompressionLevel { private get; set; }
46
47 public Output Output { private get; set; }
48
49 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
50
51 public string LayoutDirectory { private get; set; }
52
53 public bool Compressed { private get; set; }
54
55 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; }
56
57 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; }
58
59 public TableDefinitionCollection TableDefinitions { private get; set; }
60
61 public Table WixMediaTable { private get; set; }
62
63 public IEnumerable<FileTransfer> FileTransfers { get { return this.fileTransfers; } }
64
65 /// <param name="output">Output to generate image for.</param>
66 /// <param name="fileTransfers">Array of files to be transfered.</param>
67 /// <param name="layoutDirectory">The directory in which the image should be layed out.</param>
68 /// <param name="compressed">Flag if source image should be compressed.</param>
69 /// <returns>The uncompressed file rows.</returns>
70 public void Execute()
71 {
72 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
73
74 this.lastCabinetAddedToMediaTable = new Dictionary<string, string>();
75
76 this.SetCabbingThreadCount();
77
78 // Send Binder object to Facilitate NewCabNamesCallBack Callback
79 CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack));
80
81 // Supply Compile MediaTemplate Attributes to Cabinet Builder
82 int MaximumCabinetSizeForLargeFileSplitting;
83 int MaximumUncompressedMediaSize;
84 this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize);
85 cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting;
86 cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize;
87
88 foreach (var entry in this.FileRowsByCabinet)
89 {
90 MediaRow mediaRow = entry.Key;
91 IEnumerable<FileFacade> files = entry.Value;
92 CompressionLevel compressionLevel = this.DefaultCompressionLevel;
93
94 WixMediaRow wixMediaRow = null;
95 string mediaLayoutFolder = null;
96
97 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow))
98 {
99 mediaLayoutFolder = wixMediaRow.Layout;
100
101 if (wixMediaRow.CompressionLevel.HasValue)
102 {
103 compressionLevel = wixMediaRow.CompressionLevel.Value;
104 }
105 }
106
107 string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory);
108
109 CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers);
110 if (null != cabinetWorkItem)
111 {
112 cabinetBuilder.Enqueue(cabinetWorkItem);
113 }
114 }
115
116 // stop processing if an error previously occurred
117 if (Messaging.Instance.EncounteredError)
118 {
119 return;
120 }
121
122 // create queued cabinets with multiple threads
123 cabinetBuilder.CreateQueuedCabinets();
124 if (Messaging.Instance.EncounteredError)
125 {
126 return;
127 }
128 }
129
130 /// <summary>
131 /// Sets the thead count to the number of processors if the current thread count is set to 0.
132 /// </summary>
133 /// <remarks>The thread count value must be greater than 0 otherwise and exception will be thrown.</remarks>
134 private void SetCabbingThreadCount()
135 {
136 // default the number of cabbing threads to the number of processors if it wasn't specified
137 if (0 == this.CabbingThreadCount)
138 {
139 string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
140
141 try
142 {
143 if (null != numberOfProcessors)
144 {
145 this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat);
146
147 if (0 >= this.CabbingThreadCount)
148 {
149 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
150 }
151 }
152 else // default to 1 if the environment variable is not set
153 {
154 this.CabbingThreadCount = 1;
155 }
156
157 Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString()));
158 }
159 catch (ArgumentException)
160 {
161 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
162 }
163 catch (FormatException)
164 {
165 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
166 }
167 }
168 }
169
170
171 /// <summary>
172 /// Creates a work item to create a cabinet.
173 /// </summary>
174 /// <param name="output">Output for the current database.</param>
175 /// <param name="cabinetDir">Directory to create cabinet in.</param>
176 /// <param name="mediaRow">MediaRow containing information about the cabinet.</param>
177 /// <param name="fileFacades">Collection of files in this cabinet.</param>
178 /// <param name="fileTransfers">Array of files to be transfered.</param>
179 /// <returns>created CabinetWorkItem object</returns>
180 private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers)
181 {
182 CabinetWorkItem cabinetWorkItem = null;
183 string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet);
184
185 // check for an empty cabinet
186 if (!fileFacades.Any())
187 {
188 string cabinetName = mediaRow.Cabinet;
189
190 // remove the leading '#' from the embedded cabinet name to make the warning easier to understand
191 if (cabinetName.StartsWith("#", StringComparison.Ordinal))
192 {
193 cabinetName = cabinetName.Substring(1);
194 }
195
196 // If building a patch, remind them to run -p for torch.
197 if (OutputType.Patch == output.Type)
198 {
199 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true));
200 }
201 else
202 {
203 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName));
204 }
205 }
206
207 ResolvedCabinet resolvedCabinet = this.ResolveCabinet(tempCabinetFileX, fileFacades);
208
209 // create a cabinet work item if it's not being skipped
210 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
211 {
212 int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet).
213
214 cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/);
215 }
216 else // reuse the cabinet from the cabinet cache.
217 {
218 Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path));
219
220 try
221 {
222 // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The
223 // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that
224 // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from
225 // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output)
226 // causing the project to look like it perpetually needs a rebuild until all of the reused
227 // cabinets get newer timestamps.
228 File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now);
229 }
230 catch (Exception e)
231 {
232 Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message));
233 }
234 }
235
236 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
237 {
238 Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]);
239
240 Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers);
241 streamRow[0] = mediaRow.Cabinet.Substring(1);
242 streamRow[1] = resolvedCabinet.Path;
243 }
244 else
245 {
246 string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet);
247 FileTransfer transfer;
248 if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer))
249 {
250 transfer.Built = true;
251 fileTransfers.Add(transfer);
252 }
253 }
254
255 return cabinetWorkItem;
256 }
257
258 private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades)
259 {
260 ResolvedCabinet resolved = null;
261
262 List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList();
263
264 foreach (IBinderFileManager fileManager in this.FileManagers)
265 {
266 resolved = fileManager.ResolveCabinet(cabinetPath, filesWithPath);
267 if (null != resolved)
268 {
269 break;
270 }
271 }
272
273 return resolved;
274 }
275
276 /// <summary>
277 /// Delegate for Cabinet Split Callback
278 /// </summary>
279 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
280 internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken);
281
282 /// <summary>
283 /// Call back to Add File Transfer for new Cab and add new Cab to Media table
284 /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe
285 /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored
286 /// </summary>
287 /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param>
288 /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param>
289 /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param>
290 internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken)
291 {
292 // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads
293 Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback");
294 try
295 {
296 if (!mutex.WaitOne(0, false)) // Check if you can get the lock
297 {
298 // Cound not get the Lock
299 Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel());
300 mutex.WaitOne(); // Wait on other thread
301 }
302
303 string firstCabinetName = firstCabName + ".cab";
304 string newCabinetName = newCabName;
305 bool transferAdded = false; // Used for Error Handling
306
307 // Create File Transfer for new Cabinet using transfer of Base Cabinet
308 foreach (FileTransfer transfer in this.FileTransfers)
309 {
310 if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase))
311 {
312 string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName);
313 string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName);
314
315 FileTransfer newTransfer;
316 if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer))
317 {
318 newTransfer.Built = true;
319 this.fileTransfers.Add(newTransfer);
320 transferAdded = true;
321 break;
322 }
323 }
324 }
325
326 // Check if File Transfer was added
327 if (!transferAdded)
328 {
329 throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName));
330 }
331
332 // Add the new Cabinets to media table using LastSequence of Base Cabinet
333 Table mediaTable = this.Output.Tables["Media"];
334 Table wixFileTable = this.Output.Tables["WixFile"];
335 int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain
336 int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain
337 bool lastSplitCabinetFound = false; // Used for Error Handling
338
339 string lastCabinetOfThisSequence = String.Empty;
340 // Get the Value of Last Cabinet Added in this split Sequence from Dictionary
341 if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence))
342 {
343 // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence
344 lastCabinetOfThisSequence = firstCabinetName;
345 }
346
347 foreach (MediaRow mediaRow in mediaTable.Rows)
348 {
349 // Get details for the Last Cabinet Added in this Split Sequence
350 if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
351 {
352 lastSequenceForLastSplitCabAdded = mediaRow.LastSequence;
353 diskIDForLastSplitCabAdded = mediaRow.DiskId;
354 lastSplitCabinetFound = true;
355 }
356
357 // Check for Name Collision for the new Cabinet added
358 if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
359 {
360 // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row
361 throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName));
362 }
363 }
364
365 // Check if the last Split Cabinet was found in the Media Table
366 if (!lastSplitCabinetFound)
367 {
368 throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence));
369 }
370
371 // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort
372 // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with
373 // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction
374 MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null);
375 newMediaRow.Cabinet = newCabinetName;
376 newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion
377 newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded;
378
379 // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique
380 foreach (MediaRow mediaRow in mediaTable.Rows)
381 {
382 // Check if this row comes after inserted row and it is not the new cabinet inserted row
383 if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
384 {
385 mediaRow.DiskId++; // Increment DiskID
386 }
387 }
388
389 // Now Increment DiskID for All files Rows so that they refer to the right Media Row
390 foreach (WixFileRow wixFileRow in wixFileTable.Rows)
391 {
392 // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet
393 // This check will work as we have only one large file in every splitting cabinet
394 // If we want to support splitting cabinet with more large files we need to update this code
395 if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase))
396 {
397 wixFileRow.DiskId++; // Increment DiskID
398 }
399 }
400
401 // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback
402 this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName;
403
404 mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails
405 }
406 finally
407 {
408 // Releasing the Mutex here
409 mutex.ReleaseMutex();
410 }
411 }
412
413
414 /// <summary>
415 /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides
416 /// </summary>
417 /// <param name="output">Output to generate image for.</param>
418 /// <param name="fileRows">The indexed file rows.</param>
419 private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize)
420 {
421 // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size
422 string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
423 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
424 int maxCabSizeForLargeFileInMB = 0;
425 int maxPreCompressedSizeInMB = 0;
426 ulong testOverFlow = 0;
427
428 // Supply Compile MediaTemplate Attributes to Cabinet Builder
429 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
430 if (mediaTemplateTable != null)
431 {
432 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
433
434 // Get the Value for Max Cab Size for File Splitting
435 try
436 {
437 // Override authored mcslfs value if environment variable is authored.
438 if (!String.IsNullOrEmpty(mcslfsString))
439 {
440 maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString);
441 }
442 else
443 {
444 maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting;
445 }
446 testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024;
447 }
448 catch (FormatException)
449 {
450 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString));
451 }
452 catch (OverflowException)
453 {
454 throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, CompilerCore.MaxValueOfMaxCabSizeForLargeFileSplitting));
455 }
456
457 try
458 {
459 // Override authored mums value if environment variable is authored.
460 if (!String.IsNullOrEmpty(mumsString))
461 {
462 maxPreCompressedSizeInMB = Int32.Parse(mumsString);
463 }
464 else
465 {
466 maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
467 }
468 testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024;
469 }
470 catch (FormatException)
471 {
472 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
473 }
474 catch (OverflowException)
475 {
476 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB));
477 }
478
479 maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB;
480 maxUncompressedMediaSize = maxPreCompressedSizeInMB;
481 }
482 else
483 {
484 maxCabSizeForLargeFileSplitting = 0;
485 maxUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize;
486 }
487 }
488 }
489}
diff --git a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs
deleted file mode 100644
index 933a1ea8..00000000
--- a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs
+++ /dev/null
@@ -1,86 +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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11
12 /// <summary>
13 /// Creates delta patches and updates the appropriate rows to point to the newly generated patches.
14 /// </summary>
15 internal class CreateDeltaPatchesCommand : ICommand
16 {
17 public IEnumerable<FileFacade> FileFacades { private get; set; }
18
19 public Table WixPatchIdTable { private get; set; }
20
21 public string TempFilesLocation { private get; set; }
22
23 public void Execute()
24 {
25 bool optimizePatchSizeForLargeFiles = false;
26 PatchAPI.PatchInterop.PatchSymbolFlagsType apiPatchingSymbolFlags = 0;
27
28 if (null != this.WixPatchIdTable)
29 {
30 Row row = this.WixPatchIdTable.Rows[0];
31 if (null != row)
32 {
33 if (null != row[2])
34 {
35 optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture));
36 }
37
38 if (null != row[3])
39 {
40 apiPatchingSymbolFlags = (PatchAPI.PatchInterop.PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture);
41 }
42 }
43 }
44
45 foreach (FileFacade facade in this.FileFacades)
46 {
47 if (RowOperation.Modify == facade.File.Operation &&
48 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile))
49 {
50 string deltaBase = String.Concat("delta_", facade.File.File);
51 string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf"));
52 string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd"));
53
54 bool retainRangeWarning = false;
55
56 if (PatchAPI.PatchInterop.CreateDelta(
57 deltaFile,
58 facade.WixFile.Source,
59 facade.DeltaPatchFile.Symbols,
60 facade.DeltaPatchFile.RetainOffsets,
61 new[] { facade.WixFile.PreviousSource },
62 facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }),
63 facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }),
64 facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }),
65 facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }),
66 facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }),
67 apiPatchingSymbolFlags,
68 optimizePatchSizeForLargeFiles,
69 out retainRangeWarning))
70 {
71 PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile);
72
73 facade.WixFile.Source = deltaFile;
74 facade.WixFile.DeltaPatchHeaderSource = headerFile;
75 }
76
77 if (retainRangeWarning)
78 {
79 // TODO: get patch family to add to warning message for PatchWiz parity.
80 Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File));
81 }
82 }
83 }
84 }
85 }
86}
diff --git a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs
deleted file mode 100644
index 5db2768b..00000000
--- a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs
+++ /dev/null
@@ -1,68 +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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class CreateSpecialPropertiesCommand : ICommand
11 {
12 public Table PropertyTable { private get; set; }
13
14 public Table WixPropertyTable { private get; set; }
15
16 public void Execute()
17 {
18 // Create the special properties.
19 if (null != this.WixPropertyTable)
20 {
21 // Create lists of the properties that contribute to the special lists of properties.
22 SortedSet<string> adminProperties = new SortedSet<string>();
23 SortedSet<string> secureProperties = new SortedSet<string>();
24 SortedSet<string> hiddenProperties = new SortedSet<string>();
25
26 foreach (WixPropertyRow wixPropertyRow in this.WixPropertyTable.Rows)
27 {
28 if (wixPropertyRow.Admin)
29 {
30 adminProperties.Add(wixPropertyRow.Id);
31 }
32
33 if (wixPropertyRow.Hidden)
34 {
35 hiddenProperties.Add(wixPropertyRow.Id);
36 }
37
38 if (wixPropertyRow.Secure)
39 {
40 secureProperties.Add(wixPropertyRow.Id);
41 }
42 }
43
44 Table propertyTable = this.PropertyTable;
45 if (0 < adminProperties.Count)
46 {
47 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
48 row.Property = "AdminProperties";
49 row.Value = String.Join(";", adminProperties);
50 }
51
52 if (0 < secureProperties.Count)
53 {
54 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
55 row.Property = "SecureCustomProperties";
56 row.Value = String.Join(";", secureProperties);
57 }
58
59 if (0 < hiddenProperties.Count)
60 {
61 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
62 row.Property = "MsiHiddenProperties";
63 row.Value = String.Join(";", hiddenProperties);
64 }
65 }
66 }
67 }
68}
diff --git a/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs
deleted file mode 100644
index bee1488b..00000000
--- a/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs
+++ /dev/null
@@ -1,225 +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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Runtime.InteropServices;
12 using WixToolset.Cab;
13 using WixToolset.Data;
14 using WixToolset.Data.Rows;
15 using WixToolset.MergeMod;
16 using WixToolset.Msi;
17 using WixToolset.Core.Native;
18
19 /// <summary>
20 /// Retrieve files information and extract them from merge modules.
21 /// </summary>
22 internal class ExtractMergeModuleFilesCommand : ICommand
23 {
24 public IEnumerable<FileFacade> FileFacades { private get; set; }
25
26 public Table FileTable { private get; set; }
27
28 public Table WixFileTable { private get; set; }
29
30 public Table WixMergeTable { private get; set; }
31
32 public int OutputInstallerVersion { private get; set; }
33
34 public bool SuppressLayout { private get; set; }
35
36 public string TempFilesLocation { private get; set; }
37
38 public IEnumerable<FileFacade> MergeModulesFileFacades { get; private set; }
39
40 public void Execute()
41 {
42 List<FileFacade> mergeModulesFileFacades = new List<FileFacade>();
43
44 IMsmMerge2 merge = MsmInterop.GetMsmMerge();
45
46 // Index all of the file rows to be able to detect collisions with files in the Merge Modules.
47 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions
48 // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out
49 // there are other cases where we need all the file rows indexed, however they are not common cases.
50 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let
51 // this case be slightly more expensive because the cost of maintaining an indexed file row collection
52 // is a lot more costly for the common cases.
53 Dictionary<string, FileFacade> indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal);
54
55 foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows)
56 {
57 bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades);
58
59 // If the module has files and creating layout
60 if (containsFiles && !this.SuppressLayout)
61 {
62 this.ExtractFilesFromMergeModule(merge, wixMergeRow);
63 }
64 }
65
66 this.MergeModulesFileFacades = mergeModulesFileFacades;
67 }
68
69 private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades)
70 {
71 bool containsFiles = false;
72
73 try
74 {
75 // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module.
76 using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly))
77 {
78 if (db.TableExists("File") && db.TableExists("Component"))
79 {
80 Dictionary<string, FileFacade> uniqueModuleFileIdentifiers = new Dictionary<string, FileFacade>(StringComparer.OrdinalIgnoreCase);
81
82 using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
83 {
84 // add each file row from the merge module into the file row collection (check for errors along the way)
85 while (true)
86 {
87 using (Record record = view.Fetch())
88 {
89 if (null == record)
90 {
91 break;
92 }
93
94 // NOTE: this is very tricky - the merge module file rows are not added to the
95 // file table because they should not be created via idt import. Instead, these
96 // rows are created by merging in the actual modules.
97 FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
98 fileRow.File = record[1];
99 fileRow.Compressed = wixMergeRow.FileCompression;
100
101 WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
102 wixFileRow.Directory = record[2];
103 wixFileRow.DiskId = wixMergeRow.DiskId;
104 wixFileRow.PatchGroup = -1;
105 wixFileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), Path.DirectorySeparatorChar, record[1]);
106
107 FileFacade mergeModuleFileFacade = new FileFacade(true, fileRow, wixFileRow);
108
109 FileFacade collidingFacade;
110
111 // If case-sensitive collision with another merge module or a user-authored file identifier.
112 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade))
113 {
114 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File));
115 }
116 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
117 {
118 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File));
119 }
120 else // no collision
121 {
122 mergeModulesFileFacades.Add(mergeModuleFileFacade);
123
124 // Keep updating the indexes as new rows are added.
125 indexedFileFacades.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade);
126 uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade);
127 }
128
129 containsFiles = true;
130 }
131 }
132 }
133 }
134
135 // Get the summary information to detect the Schema
136 using (SummaryInformation summaryInformation = new SummaryInformation(db))
137 {
138 string moduleInstallerVersionString = summaryInformation.GetProperty(14);
139
140 try
141 {
142 int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture);
143 if (moduleInstallerVersion > this.OutputInstallerVersion)
144 {
145 Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion));
146 }
147 }
148 catch (FormatException)
149 {
150 throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString));
151 }
152 }
153 }
154 }
155 catch (FileNotFoundException)
156 {
157 throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
158 }
159 catch (Win32Exception)
160 {
161 throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile));
162 }
163
164 return containsFiles;
165 }
166
167 private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow)
168 {
169 bool moduleOpen = false;
170 short mergeLanguage;
171
172 try
173 {
174 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
175 }
176 catch (System.FormatException)
177 {
178 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language));
179 return;
180 }
181
182 try
183 {
184 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
185 moduleOpen = true;
186
187 string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat);
188
189 // extract the module cabinet, then explode all of the files to a temp directory
190 string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab");
191 merge.ExtractCAB(moduleCabPath);
192
193 string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId);
194 Directory.CreateDirectory(mergeIdPath);
195
196 using (WixExtractCab extractCab = new WixExtractCab())
197 {
198 try
199 {
200 extractCab.Extract(moduleCabPath, mergeIdPath);
201 }
202 catch (FileNotFoundException)
203 {
204 throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
205 }
206 catch
207 {
208 throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
209 }
210 }
211 }
212 catch (COMException ce)
213 {
214 throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message));
215 }
216 finally
217 {
218 if (moduleOpen)
219 {
220 merge.CloseModule();
221 }
222 }
223 }
224 }
225}
diff --git a/src/WixToolset.Core/Bind/Databases/FileFacade.cs b/src/WixToolset.Core/Bind/Databases/FileFacade.cs
deleted file mode 100644
index 37115c97..00000000
--- a/src/WixToolset.Core/Bind/Databases/FileFacade.cs
+++ /dev/null
@@ -1,44 +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.Bind.Databases
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Data.Rows;
8
9 public class FileFacade
10 {
11 public FileFacade(FileRow file, WixFileRow wixFile, WixDeltaPatchFileRow deltaPatchFile)
12 {
13 this.File = file;
14 this.WixFile = wixFile;
15 this.DeltaPatchFile = deltaPatchFile;
16 }
17
18 public FileFacade(bool fromModule, FileRow file, WixFileRow wixFile)
19 {
20 this.FromModule = fromModule;
21 this.File = file;
22 this.WixFile = wixFile;
23 }
24
25 public bool FromModule { get; private set; }
26
27 public FileRow File { get; private set; }
28
29 public WixFileRow WixFile { get; private set; }
30
31 public WixDeltaPatchFileRow DeltaPatchFile { get; private set; }
32
33 /// <summary>
34 /// Gets the set of MsiAssemblyName rows created for this file.
35 /// </summary>
36 /// <value>RowCollection of MsiAssemblyName table.</value>
37 public List<Row> AssemblyNames { get; set; }
38
39 /// <summary>
40 /// Gets or sets the MsiFileHash row for this file.
41 /// </summary>
42 public Row Hash { get; set; }
43 }
44}
diff --git a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs
deleted file mode 100644
index b6bcd3af..00000000
--- a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs
+++ /dev/null
@@ -1,148 +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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11
12 internal class GetFileFacadesCommand : ICommand
13 {
14 public Table FileTable { private get; set; }
15
16 public Table WixFileTable { private get; set; }
17
18 public Table WixDeltaPatchFileTable { private get; set; }
19
20 public Table WixDeltaPatchSymbolPathsTable { private get; set; }
21
22 public List<FileFacade> FileFacades { get; private set; }
23
24 public void Execute()
25 {
26 List<FileFacade> facades = new List<FileFacade>(this.FileTable.Rows.Count);
27
28 RowDictionary<WixFileRow> wixFiles = new RowDictionary<WixFileRow>(this.WixFileTable);
29 RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles = new RowDictionary<WixDeltaPatchFileRow>(this.WixDeltaPatchFileTable);
30
31 foreach (FileRow file in this.FileTable.Rows)
32 {
33 WixDeltaPatchFileRow deltaPatchFile = null;
34
35 deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile);
36
37 facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile));
38 }
39
40 if (null != this.WixDeltaPatchSymbolPathsTable)
41 {
42 this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
43 }
44
45 this.FileFacades = facades;
46 }
47
48 /// <summary>
49 /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows.
50 /// </summary>
51 public RowDictionary<WixDeltaPatchFileRow> ResolveDeltaPatchSymbolPaths(RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles, IEnumerable<FileFacade> facades)
52 {
53 ILookup<string, FileFacade> filesByComponent = null;
54 ILookup<string, FileFacade> filesByDirectory = null;
55 ILookup<string, FileFacade> filesByDiskId = null;
56
57 foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs<WixDeltaPatchSymbolPathsRow>().OrderBy(r => r.Type))
58 {
59 switch (row.Type)
60 {
61 case SymbolPathType.File:
62 this.MergeSymbolPaths(row, deltaPatchFiles[row.Id]);
63 break;
64
65 case SymbolPathType.Component:
66 if (null == filesByComponent)
67 {
68 filesByComponent = facades.ToLookup(f => f.File.Component);
69 }
70
71 foreach (FileFacade facade in filesByComponent[row.Id])
72 {
73 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
74 }
75 break;
76
77 case SymbolPathType.Directory:
78 if (null == filesByDirectory)
79 {
80 filesByDirectory = facades.ToLookup(f => f.WixFile.Directory);
81 }
82
83 foreach (FileFacade facade in filesByDirectory[row.Id])
84 {
85 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
86 }
87 break;
88
89 case SymbolPathType.Media:
90 if (null == filesByDiskId)
91 {
92 filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture));
93 }
94
95 foreach (FileFacade facade in filesByDiskId[row.Id])
96 {
97 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
98 }
99 break;
100
101 case SymbolPathType.Product:
102 foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values)
103 {
104 this.MergeSymbolPaths(row, fileRow);
105 }
106 break;
107
108 default:
109 // error
110 break;
111 }
112 }
113
114 return deltaPatchFiles;
115 }
116
117 /// <summary>
118 /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row.
119 /// </summary>
120 /// <param name="row">Row from the WixPatchSymbolsPaths table.</param>
121 /// <param name="file">FileRow into which to set symbol information.</param>
122 /// <comment>This includes PreviousData as well.</comment>
123 private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file)
124 {
125 if (null == file.Symbols)
126 {
127 file.Symbols = row.SymbolPaths;
128 }
129 else
130 {
131 file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths);
132 }
133
134 Field field = row.Fields[2];
135 if (null != field.PreviousData)
136 {
137 if (null == file.PreviousSymbols)
138 {
139 file.PreviousSymbols = field.PreviousData;
140 }
141 else
142 {
143 file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData);
144 }
145 }
146 }
147 }
148}
diff --git a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs b/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs
deleted file mode 100644
index 035ef059..00000000
--- a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs
+++ /dev/null
@@ -1,350 +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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Diagnostics;
10 using System.Globalization;
11 using System.IO;
12 using System.Linq;
13 using System.Runtime.InteropServices;
14 using System.Text;
15 using System.Xml;
16 using System.Xml.XPath;
17 using WixToolset.Clr.Interop;
18 using WixToolset.Data;
19 using WixToolset.Data.Rows;
20 using WixToolset.MergeMod;
21 using WixToolset.Msi;
22 using WixToolset.Core.Native;
23
24 /// <summary>
25 /// Update file information.
26 /// </summary>
27 internal class MergeModulesCommand : ICommand
28 {
29 public IEnumerable<FileFacade> FileFacades { private get; set; }
30
31 public Output Output { private get; set; }
32
33 public string OutputPath { private get; set; }
34
35 public IEnumerable<string> SuppressedTableNames { private get; set; }
36
37 public string TempFilesLocation { private get; set; }
38
39 public void Execute()
40 {
41 Debug.Assert(OutputType.Product == this.Output.Type);
42
43 Table wixMergeTable = this.Output.Tables["WixMerge"];
44 Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"];
45
46 // check for merge rows to see if there is any work to do
47 if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count)
48 {
49 return;
50 }
51
52 IMsmMerge2 merge = null;
53 bool commit = true;
54 bool logOpen = false;
55 bool databaseOpen = false;
56 string logPath = null;
57 try
58 {
59 merge = MsmInterop.GetMsmMerge();
60
61 logPath = Path.Combine(this.TempFilesLocation, "merge.log");
62 merge.OpenLog(logPath);
63 logOpen = true;
64
65 merge.OpenDatabase(this.OutputPath);
66 databaseOpen = true;
67
68 // process all the merge rows
69 foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows)
70 {
71 bool moduleOpen = false;
72
73 try
74 {
75 short mergeLanguage;
76
77 try
78 {
79 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
80 }
81 catch (System.FormatException)
82 {
83 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language));
84 continue;
85 }
86
87 Messaging.Instance.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage));
88 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
89 moduleOpen = true;
90
91 // If there is merge configuration data, create a callback object to contain it all.
92 ConfigurationCallback callback = null;
93 if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData))
94 {
95 callback = new ConfigurationCallback(wixMergeRow.ConfigurationData);
96 }
97
98 // merge the module into the database that's being built
99 Messaging.Instance.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile));
100 merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback);
101
102 // connect any non-primary features
103 if (null != wixFeatureModulesTable)
104 {
105 foreach (Row row in wixFeatureModulesTable.Rows)
106 {
107 if (wixMergeRow.Id == (string)row[1])
108 {
109 Messaging.Instance.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0]));
110 merge.Connect((string)row[0]);
111 }
112 }
113 }
114 }
115 catch (COMException)
116 {
117 commit = false;
118 }
119 finally
120 {
121 IMsmErrors mergeErrors = merge.Errors;
122
123 // display all the errors encountered during the merge operations for this module
124 for (int i = 1; i <= mergeErrors.Count; i++)
125 {
126 IMsmError mergeError = mergeErrors[i];
127 StringBuilder databaseKeys = new StringBuilder();
128 StringBuilder moduleKeys = new StringBuilder();
129
130 // build a string of the database keys
131 for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++)
132 {
133 if (1 != j)
134 {
135 databaseKeys.Append(';');
136 }
137 databaseKeys.Append(mergeError.DatabaseKeys[j]);
138 }
139
140 // build a string of the module keys
141 for (int j = 1; j <= mergeError.ModuleKeys.Count; j++)
142 {
143 if (1 != j)
144 {
145 moduleKeys.Append(';');
146 }
147 moduleKeys.Append(mergeError.ModuleKeys[j]);
148 }
149
150 // display the merge error based on the msm error type
151 switch (mergeError.Type)
152 {
153 case MsmErrorType.msmErrorExclusion:
154 Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString()));
155 break;
156 case MsmErrorType.msmErrorFeatureRequired:
157 Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id));
158 break;
159 case MsmErrorType.msmErrorLanguageFailed:
160 Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
161 break;
162 case MsmErrorType.msmErrorLanguageUnsupported:
163 Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
164 break;
165 case MsmErrorType.msmErrorResequenceMerge:
166 Messaging.Instance.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
167 break;
168 case MsmErrorType.msmErrorTableMerge:
169 if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table
170 {
171 Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
172 }
173 break;
174 case MsmErrorType.msmErrorPlatformMismatch:
175 Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
176 break;
177 default:
178 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace));
179 break;
180 }
181 }
182
183 if (0 >= mergeErrors.Count && !commit)
184 {
185 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace));
186 }
187
188 if (moduleOpen)
189 {
190 merge.CloseModule();
191 }
192 }
193 }
194 }
195 finally
196 {
197 if (databaseOpen)
198 {
199 merge.CloseDatabase(commit);
200 }
201
202 if (logOpen)
203 {
204 merge.CloseLog();
205 }
206 }
207
208 // stop processing if an error previously occurred
209 if (Messaging.Instance.EncounteredError)
210 {
211 return;
212 }
213
214 using (Database db = new Database(this.OutputPath, OpenDatabase.Direct))
215 {
216 Table suppressActionTable = this.Output.Tables["WixSuppressAction"];
217
218 // suppress individual actions
219 if (null != suppressActionTable)
220 {
221 foreach (Row row in suppressActionTable.Rows)
222 {
223 if (db.TableExists((string)row[0]))
224 {
225 string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]);
226
227 using (View view = db.OpenExecuteView(query))
228 {
229 using (Record record = view.Fetch())
230 {
231 if (null != record)
232 {
233 Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString()));
234 view.Modify(ModifyView.Delete, record);
235 }
236 }
237 }
238 }
239 }
240 }
241
242 // query for merge module actions in suppressed sequences and drop them
243 foreach (string tableName in this.SuppressedTableNames)
244 {
245 if (!db.TableExists(tableName))
246 {
247 continue;
248 }
249
250 using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
251 {
252 while (true)
253 {
254 using (Record resultRecord = view.Fetch())
255 {
256 if (null == resultRecord)
257 {
258 break;
259 }
260
261 Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName));
262 }
263 }
264 }
265
266 // drop suppressed sequences
267 using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName)))
268 {
269 }
270
271 // delete the validation rows
272 using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?")))
273 {
274 using (Record record = new Record(1))
275 {
276 record.SetString(1, tableName);
277 view.Execute(record);
278 }
279 }
280 }
281
282 // now update the Attributes column for the files from the Merge Modules
283 Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles());
284 using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
285 {
286 foreach (FileFacade file in this.FileFacades)
287 {
288 if (!file.FromModule)
289 {
290 continue;
291 }
292
293 using (Record record = new Record(1))
294 {
295 record.SetString(1, file.File.File);
296 view.Execute(record);
297 }
298
299 using (Record recordUpdate = view.Fetch())
300 {
301 if (null == recordUpdate)
302 {
303 throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module.");
304 }
305
306 recordUpdate.SetInteger(1, file.File.Sequence);
307
308 // update the file attributes to match the compression specified
309 // on the Merge element or on the Package element
310 int attributes = 0;
311
312 // get the current value if its not null
313 if (!recordUpdate.IsNull(2))
314 {
315 attributes = recordUpdate.GetInteger(2);
316 }
317
318 if (YesNoType.Yes == file.File.Compressed)
319 {
320 // these are mutually exclusive
321 attributes |= MsiInterop.MsidbFileAttributesCompressed;
322 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
323 }
324 else if (YesNoType.No == file.File.Compressed)
325 {
326 // these are mutually exclusive
327 attributes |= MsiInterop.MsidbFileAttributesNoncompressed;
328 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
329 }
330 else // not specified
331 {
332 Debug.Assert(YesNoType.NotSet == file.File.Compressed);
333
334 // clear any compression bits
335 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
336 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
337 }
338
339 recordUpdate.SetInteger(2, attributes);
340
341 view.Modify(ModifyView.Update, recordUpdate);
342 }
343 }
344 }
345
346 db.Commit();
347 }
348 }
349 }
350}
diff --git a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs
deleted file mode 100644
index dd7b85b7..00000000
--- a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs
+++ /dev/null
@@ -1,115 +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.Bind.Databases
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11 using WixToolset.Msi;
12 using WixToolset.Core.Native;
13
14 /// <summary>
15 /// Defines the file transfers necessary to layout the uncompressed files.
16 /// </summary>
17 internal class ProcessUncompressedFilesCommand : ICommand
18 {
19 public string DatabasePath { private get; set; }
20
21 public IEnumerable<FileFacade> FileFacades { private get; set; }
22
23 public RowDictionary<MediaRow> MediaRows { private get; set; }
24
25 public string LayoutDirectory { private get; set; }
26
27 public bool Compressed { private get; set; }
28
29 public bool LongNamesInImage { private get; set; }
30
31 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; }
32
33 public Table WixMediaTable { private get; set; }
34
35 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
36
37 public void Execute()
38 {
39 List<FileTransfer> fileTransfers = new List<FileTransfer>();
40
41 Hashtable directories = new Hashtable();
42
43 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
44
45 using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
46 {
47 using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
48 {
49 while (true)
50 {
51 using (Record directoryRecord = directoryView.Fetch())
52 {
53 if (null == directoryRecord)
54 {
55 break;
56 }
57
58 string sourceName = Installer.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage);
59
60 directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName));
61 }
62 }
63 }
64
65 using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?"))
66 {
67 using (Record fileQueryRecord = new Record(1))
68 {
69 // for each file in the array of uncompressed files
70 foreach (FileFacade facade in this.FileFacades)
71 {
72 MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId);
73 string relativeFileLayoutPath = null;
74
75 WixMediaRow wixMediaRow = null;
76 string mediaLayoutFolder = null;
77
78 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow))
79 {
80 mediaLayoutFolder = wixMediaRow.Layout;
81 }
82
83 string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory);
84
85 // setup up the query record and find the appropriate file in the
86 // previously executed file view
87 fileQueryRecord[1] = facade.File.File;
88 fileView.Execute(fileQueryRecord);
89
90 using (Record fileRecord = fileView.Fetch())
91 {
92 if (null == fileRecord)
93 {
94 throw new WixException(WixErrors.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File));
95 }
96
97 relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage);
98 }
99
100 // finally put together the base media layout path and the relative file layout path
101 string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath);
102 FileTransfer transfer;
103 if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer))
104 {
105 fileTransfers.Add(transfer);
106 }
107 }
108 }
109 }
110 }
111
112 this.FileTransfers = fileTransfers;
113 }
114 }
115}
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs
deleted file mode 100644
index 9e17ee02..00000000
--- a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs
+++ /dev/null
@@ -1,80 +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.Bind.Databases
4{
5 using System;
6 using System.IO;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class UpdateControlTextCommand : ICommand
11 {
12 public Table BBControlTable { private get; set; }
13
14 public Table WixBBControlTable { private get; set; }
15
16 public Table ControlTable { private get; set; }
17
18 public Table WixControlTable { private get; set; }
19
20 public void Execute()
21 {
22 if (null != this.WixBBControlTable)
23 {
24 RowDictionary<BBControlRow> bbControlRows = new RowDictionary<BBControlRow>(this.BBControlTable);
25 foreach (Row wixRow in this.WixBBControlTable.Rows)
26 {
27 BBControlRow bbControlRow = bbControlRows.Get(wixRow.GetPrimaryKey());
28 bbControlRow.Text = this.ReadTextFile(bbControlRow.SourceLineNumbers, wixRow.FieldAsString(2));
29 }
30 }
31
32 if (null != this.WixControlTable)
33 {
34 RowDictionary<ControlRow> controlRows = new RowDictionary<ControlRow>(this.ControlTable);
35 foreach (Row wixRow in this.WixControlTable.Rows)
36 {
37 ControlRow controlRow = controlRows.Get(wixRow.GetPrimaryKey());
38 controlRow.Text = this.ReadTextFile(controlRow.SourceLineNumbers, wixRow.FieldAsString(2));
39 }
40 }
41 }
42
43 /// <summary>
44 /// Reads a text file and returns the contents.
45 /// </summary>
46 /// <param name="sourceLineNumbers">Source line numbers for row from source.</param>
47 /// <param name="source">Source path to file to read.</param>
48 /// <returns>Text string read from file.</returns>
49 private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source)
50 {
51 string text = null;
52
53 try
54 {
55 using (StreamReader reader = new StreamReader(source))
56 {
57 text = reader.ReadToEnd();
58 }
59 }
60 catch (DirectoryNotFoundException e)
61 {
62 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
63 }
64 catch (FileNotFoundException e)
65 {
66 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
67 }
68 catch (IOException e)
69 {
70 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
71 }
72 catch (NotSupportedException)
73 {
74 Messaging.Instance.OnMessage(WixErrors.FileNotFound(sourceLineNumbers, source));
75 }
76
77 return text;
78 }
79 }
80}
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
deleted file mode 100644
index 36818afa..00000000
--- a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
+++ /dev/null
@@ -1,532 +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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using System.Xml;
13 using System.Xml.XPath;
14 using WixToolset.Clr.Interop;
15 using WixToolset.Data;
16 using WixToolset.Data.Rows;
17 using WixToolset.Msi;
18
19 /// <summary>
20 /// Update file information.
21 /// </summary>
22 internal class UpdateFileFacadesCommand : ICommand
23 {
24 public IEnumerable<FileFacade> FileFacades { private get; set; }
25
26 public IEnumerable<FileFacade> UpdateFileFacades { private get; set; }
27
28 public string ModularizationGuid { private get; set; }
29
30 public Output Output { private get; set; }
31
32 public bool OverwriteHash { private get; set; }
33
34 public TableDefinitionCollection TableDefinitions { private get; set; }
35
36 public IDictionary<string, string> VariableCache { private get; set; }
37
38 public void Execute()
39 {
40 foreach (FileFacade file in this.UpdateFileFacades)
41 {
42 this.UpdateFileFacade(file);
43 }
44 }
45
46 private void UpdateFileFacade(FileFacade file)
47 {
48 FileInfo fileInfo = null;
49 try
50 {
51 fileInfo = new FileInfo(file.WixFile.Source);
52 }
53 catch (ArgumentException)
54 {
55 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
56 return;
57 }
58 catch (PathTooLongException)
59 {
60 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
61 return;
62 }
63 catch (NotSupportedException)
64 {
65 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
66 return;
67 }
68
69 if (!fileInfo.Exists)
70 {
71 Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source));
72 return;
73 }
74
75 using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
76 {
77 if (Int32.MaxValue < fileStream.Length)
78 {
79 throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source));
80 }
81
82 file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
83 }
84
85 string version = null;
86 string language = null;
87 try
88 {
89 Installer.GetFileVersion(fileInfo.FullName, out version, out language);
90 }
91 catch (Win32Exception e)
92 {
93 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
94 {
95 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
96 }
97 else
98 {
99 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
100 }
101 }
102
103 // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install.
104 if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
105 {
106 if (!this.OverwriteHash)
107 {
108 // not overwriting hash, so don't do the rest of these options.
109 }
110 else if (null != file.File.Version)
111 {
112 // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
113 // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up.
114 // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
115 // all the file rows) for a relatively uncommon situation. Let's not do that.
116 //
117 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
118 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
119 if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal)))
120 {
121 Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File));
122 }
123 }
124 else
125 {
126 if (null != file.File.Language)
127 {
128 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
129 }
130
131 int[] hash;
132 try
133 {
134 Installer.GetFileHash(fileInfo.FullName, 0, out hash);
135 }
136 catch (Win32Exception e)
137 {
138 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
139 {
140 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
141 }
142 else
143 {
144 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
145 }
146 }
147
148 if (null == file.Hash)
149 {
150 Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]);
151 file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers);
152 }
153
154 file.Hash[0] = file.File.File;
155 file.Hash[1] = 0;
156 file.Hash[2] = hash[0];
157 file.Hash[3] = hash[1];
158 file.Hash[4] = hash[2];
159 file.Hash[5] = hash[3];
160 }
161 }
162 else // update the file row with the version and language information.
163 {
164 // If no version was provided by the user, use the version from the file itself.
165 // This is the most common case.
166 if (String.IsNullOrEmpty(file.File.Version))
167 {
168 file.File.Version = version;
169 }
170 else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
171 {
172 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
173 // the version value). We didn't find it so, we will override the default version they provided with the actual
174 // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
175 // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
176 // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
177 // CPU intensive search to save on the memory intensive index that wouldn't be used much.
178 //
179 // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
180 // That's typically even more rare than companion files so again, no index, just search.
181 file.File.Version = version;
182 }
183
184 if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language))
185 {
186 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
187 }
188 else // override the default provided by the user (usually nothing) with the actual language from the file itself.
189 {
190 file.File.Language = language;
191 }
192
193 // Populate the binder variables for this file information if requested.
194 if (null != this.VariableCache)
195 {
196 if (!String.IsNullOrEmpty(file.File.Version))
197 {
198 string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File));
199 this.VariableCache[key] = file.File.Version;
200 }
201
202 if (!String.IsNullOrEmpty(file.File.Language))
203 {
204 string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, ModularizationGuid, file.File.File));
205 this.VariableCache[key] = file.File.Language;
206 }
207 }
208 }
209
210 // If this is a CLR assembly, load the assembly and get the assembly name information
211 if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType)
212 {
213 bool targetNetfx1 = false;
214 StringDictionary assemblyNameValues = new StringDictionary();
215
216 ClrInterop.IReferenceIdentity referenceIdentity = null;
217 Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid;
218 uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity);
219 if (0 == result && null != referenceIdentity)
220 {
221 string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion");
222 if (null != imageRuntimeVersion)
223 {
224 targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase);
225 }
226
227 string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral";
228 assemblyNameValues.Add("Culture", culture);
229
230 string name = referenceIdentity.GetAttribute(null, "Name");
231 if (null != name)
232 {
233 assemblyNameValues.Add("Name", name);
234 }
235
236 string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture");
237 if (null != processorArchitecture)
238 {
239 assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture);
240 }
241
242 string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken");
243 if (null != publicKeyToken)
244 {
245 bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase));
246
247 // Managed code expects "null" instead of "neutral", and
248 // this won't be installed to the GAC since it's not signed anyway.
249 assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant());
250 assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken);
251 }
252 else if (file.WixFile.AssemblyApplication == null)
253 {
254 throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component));
255 }
256
257 string assemblyVersion = referenceIdentity.GetAttribute(null, "Version");
258 if (null != version)
259 {
260 assemblyNameValues.Add("Version", assemblyVersion);
261 }
262 }
263 else
264 {
265 Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result)));
266 return;
267 }
268
269 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
270 if (assemblyNameValues.ContainsKey("name"))
271 {
272 this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]);
273 }
274
275 if (!String.IsNullOrEmpty(version))
276 {
277 this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version);
278 }
279
280 if (assemblyNameValues.ContainsKey("version"))
281 {
282 string assemblyVersion = assemblyNameValues["version"];
283
284 if (!targetNetfx1)
285 {
286 // There is a bug in v1 fusion that requires the assembly's "version" attribute
287 // to be equal to or longer than the "fileVersion" in length when its present;
288 // the workaround is to prepend zeroes to the last version number in the assembly
289 // version.
290 if (null != version && version.Length > assemblyVersion.Length)
291 {
292 string padding = new string('0', version.Length - assemblyVersion.Length);
293 string[] assemblyVersionNumbers = assemblyVersion.Split('.');
294
295 if (assemblyVersionNumbers.Length > 0)
296 {
297 assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]);
298 assemblyVersion = String.Join(".", assemblyVersionNumbers);
299 }
300 }
301 }
302
303 this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion);
304 }
305
306 if (assemblyNameValues.ContainsKey("culture"))
307 {
308 this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]);
309 }
310
311 if (assemblyNameValues.ContainsKey("publicKeyToken"))
312 {
313 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]);
314 }
315
316 if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
317 {
318 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
319 }
320
321 if (assemblyNameValues.ContainsKey("processorArchitecture"))
322 {
323 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]);
324 }
325
326 // add the assembly name to the information cache
327 if (null != this.VariableCache)
328 {
329 string fileId = BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File);
330 string key = String.Concat("assemblyfullname.", fileId);
331 string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]);
332 if (assemblyNameValues.ContainsKey("processorArchitecture"))
333 {
334 assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]);
335 }
336
337 this.VariableCache[key] = assemblyName;
338
339 // Add entries with the preserved case publicKeyToken
340 string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId);
341 this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]);
342
343 string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId);
344 this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"];
345 }
346 }
347 else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType)
348 {
349 // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through
350 // all files like this. Even though this is a rare case it looks like we might be able to index the
351 // file earlier.
352 FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal));
353 if (null == fileManifest)
354 {
355 Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest));
356 }
357
358 string win32Type = null;
359 string win32Name = null;
360 string win32Version = null;
361 string win32ProcessorArchitecture = null;
362 string win32PublicKeyToken = null;
363
364 // loading the dom is expensive we want more performant APIs than the DOM
365 // Navigator is cheaper than dom. Perhaps there is a cheaper API still.
366 try
367 {
368 XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source);
369 XPathNavigator nav = doc.CreateNavigator();
370 nav.MoveToRoot();
371
372 // this assumes a particular schema for a win32 manifest and does not
373 // provide error checking if the file does not conform to schema.
374 // The fallback case here is that nothing is added to the MsiAssemblyName
375 // table for an out of tolerance Win32 manifest. Perhaps warnings needed.
376 if (nav.MoveToFirstChild())
377 {
378 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly")
379 {
380 nav.MoveToNext();
381 }
382
383 if (nav.MoveToFirstChild())
384 {
385 bool hasNextSibling = true;
386 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling)
387 {
388 hasNextSibling = nav.MoveToNext();
389 }
390 if (!hasNextSibling)
391 {
392 Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source));
393 return;
394 }
395
396 if (nav.MoveToAttribute("type", String.Empty))
397 {
398 win32Type = nav.Value;
399 nav.MoveToParent();
400 }
401
402 if (nav.MoveToAttribute("name", String.Empty))
403 {
404 win32Name = nav.Value;
405 nav.MoveToParent();
406 }
407
408 if (nav.MoveToAttribute("version", String.Empty))
409 {
410 win32Version = nav.Value;
411 nav.MoveToParent();
412 }
413
414 if (nav.MoveToAttribute("processorArchitecture", String.Empty))
415 {
416 win32ProcessorArchitecture = nav.Value;
417 nav.MoveToParent();
418 }
419
420 if (nav.MoveToAttribute("publicKeyToken", String.Empty))
421 {
422 win32PublicKeyToken = nav.Value;
423 nav.MoveToParent();
424 }
425 }
426 }
427 }
428 catch (FileNotFoundException fe)
429 {
430 Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest"));
431 }
432 catch (XmlException xe)
433 {
434 Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message));
435 }
436
437 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
438 if (!String.IsNullOrEmpty(win32Name))
439 {
440 this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name);
441 }
442
443 if (!String.IsNullOrEmpty(win32Version))
444 {
445 this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version);
446 }
447
448 if (!String.IsNullOrEmpty(win32Type))
449 {
450 this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type);
451 }
452
453 if (!String.IsNullOrEmpty(win32ProcessorArchitecture))
454 {
455 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture);
456 }
457
458 if (!String.IsNullOrEmpty(win32PublicKeyToken))
459 {
460 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken);
461 }
462 }
463 }
464
465 /// <summary>
466 /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise
467 /// create a new row.
468 /// </summary>
469 /// <param name="assemblyNameTable">MsiAssemblyName table.</param>
470 /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
471 /// <param name="name">MsiAssemblyName name.</param>
472 /// <param name="value">MsiAssemblyName value.</param>
473 private void SetMsiAssemblyName(Table assemblyNameTable, FileFacade file, string name, string value)
474 {
475 // check for null value (this can occur when grabbing the file version from an assembly without one)
476 if (String.IsNullOrEmpty(value))
477 {
478 Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name));
479 }
480 else
481 {
482 Row assemblyNameRow = null;
483
484 // override directly authored value
485 foreach (Row row in assemblyNameTable.Rows)
486 {
487 if ((string)row[0] == file.File.Component && (string)row[1] == name)
488 {
489 assemblyNameRow = row;
490 break;
491 }
492 }
493
494 // 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.
495 if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType &&
496 String.IsNullOrEmpty(file.WixFile.AssemblyApplication) &&
497 !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase))
498 {
499 Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value));
500 }
501
502 if (null == assemblyNameRow)
503 {
504 assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers);
505 assemblyNameRow[0] = file.File.Component;
506 assemblyNameRow[1] = name;
507 assemblyNameRow[2] = value;
508
509 // put the MsiAssemblyName row in the same section as the related File row
510 assemblyNameRow.SectionId = file.File.SectionId;
511
512 if (null == file.AssemblyNames)
513 {
514 file.AssemblyNames = new List<Row>();
515 }
516
517 file.AssemblyNames.Add(assemblyNameRow);
518 }
519 else
520 {
521 assemblyNameRow[2] = value;
522 }
523
524 if (this.VariableCache != null)
525 {
526 string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant();
527 this.VariableCache[key] = (string)assemblyNameRow[2];
528 }
529 }
530 }
531 }
532}