diff options
author | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
commit | dbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch) | |
tree | 0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core.WindowsInstaller/Bind | |
parent | fbf986eb97f68396797a89fc7d40dec07b775440 (diff) | |
download | wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2 wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip |
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind')
19 files changed, 5719 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs new file mode 100644 index 00000000..23c481b7 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs | |||
@@ -0,0 +1,314 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Databases | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Globalization; | ||
8 | using WixToolset.Core.Bind; | ||
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 | ||
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.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs new file mode 100644 index 00000000..2e2c5417 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs | |||
@@ -0,0 +1,1282 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Diagnostics; | ||
9 | using System.Globalization; | ||
10 | using System.IO; | ||
11 | using System.Linq; | ||
12 | using WixToolset.Bind; | ||
13 | using WixToolset.Core.Bind; | ||
14 | using WixToolset.Core.WindowsInstaller.Databases; | ||
15 | using WixToolset.Data; | ||
16 | using WixToolset.Data.Bind; | ||
17 | using WixToolset.Data.Rows; | ||
18 | using WixToolset.Extensibility; | ||
19 | using WixToolset.Msi; | ||
20 | |||
21 | /// <summary> | ||
22 | /// Binds a databse. | ||
23 | /// </summary> | ||
24 | internal class BindDatabaseCommand | ||
25 | { | ||
26 | // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. | ||
27 | private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); | ||
28 | |||
29 | public BindDatabaseCommand(IBindContext context, Validator validator) | ||
30 | { | ||
31 | this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); | ||
32 | |||
33 | this.BindPaths = context.BindPaths; | ||
34 | this.CabbingThreadCount = context.CabbingThreadCount; | ||
35 | this.CabCachePath = context.CabCachePath; | ||
36 | this.Codepage = context.Codepage; | ||
37 | this.DefaultCompressionLevel = context.DefaultCompressionLevel; | ||
38 | this.DelayedFields = context.DelayedFields; | ||
39 | this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; | ||
40 | this.Extensions = context.Extensions; | ||
41 | this.Output = context.IntermediateRepresentation; | ||
42 | this.OutputPath = context.OutputPath; | ||
43 | this.PdbFile = context.OutputPdbPath; | ||
44 | this.IntermediateFolder = context.IntermediateFolder; | ||
45 | this.Validator = validator; | ||
46 | this.WixVariableResolver = context.WixVariableResolver; | ||
47 | |||
48 | this.BackendExtensions = context.ExtensionManager.Create<IWindowsInstallerBackendExtension>(); | ||
49 | } | ||
50 | |||
51 | private IEnumerable<BindPath> BindPaths { get; } | ||
52 | |||
53 | private int Codepage { get; } | ||
54 | |||
55 | private int CabbingThreadCount { get; } | ||
56 | |||
57 | private string CabCachePath { get; } | ||
58 | |||
59 | private CompressionLevel DefaultCompressionLevel { get; } | ||
60 | |||
61 | public IEnumerable<IDelayedField> DelayedFields { get; } | ||
62 | |||
63 | public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; } | ||
64 | |||
65 | public bool DeltaBinaryPatch { get; set; } | ||
66 | |||
67 | private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; } | ||
68 | |||
69 | private IEnumerable<IBinderExtension> Extensions { get; } | ||
70 | |||
71 | private IEnumerable<InspectorExtension> InspectorExtensions { get; } | ||
72 | |||
73 | private string PdbFile { get; } | ||
74 | |||
75 | private Output Output { get; } | ||
76 | |||
77 | private string OutputPath { get; } | ||
78 | |||
79 | private bool SuppressAddingValidationRows { get; } | ||
80 | |||
81 | private bool SuppressLayout { get; } | ||
82 | |||
83 | private TableDefinitionCollection TableDefinitions { get; } | ||
84 | |||
85 | private string IntermediateFolder { get; } | ||
86 | |||
87 | private Validator Validator { get; } | ||
88 | |||
89 | private IBindVariableResolver WixVariableResolver { get; } | ||
90 | |||
91 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
92 | |||
93 | public IEnumerable<string> ContentFilePaths { get; private set; } | ||
94 | |||
95 | public void Execute() | ||
96 | { | ||
97 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
98 | |||
99 | HashSet<string> suppressedTableNames = new HashSet<string>(); | ||
100 | |||
101 | // If there are any fields to resolve later, create the cache to populate during bind. | ||
102 | IDictionary<string, string> variableCache = null; | ||
103 | if (this.DelayedFields.Any()) | ||
104 | { | ||
105 | variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); | ||
106 | } | ||
107 | |||
108 | this.LocalizeUI(this.Output.Tables); | ||
109 | |||
110 | // Process the summary information table before the other tables. | ||
111 | bool compressed; | ||
112 | bool longNames; | ||
113 | int installerVersion; | ||
114 | string modularizationGuid; | ||
115 | { | ||
116 | BindSummaryInfoCommand command = new BindSummaryInfoCommand(); | ||
117 | command.Output = this.Output; | ||
118 | command.Execute(); | ||
119 | |||
120 | compressed = command.Compressed; | ||
121 | longNames = command.LongNames; | ||
122 | installerVersion = command.InstallerVersion; | ||
123 | modularizationGuid = command.ModularizationGuid; | ||
124 | } | ||
125 | |||
126 | // Stop processing if an error previously occurred. | ||
127 | if (Messaging.Instance.EncounteredError) | ||
128 | { | ||
129 | return; | ||
130 | } | ||
131 | |||
132 | // Modularize identifiers and add tables with real streams to the import tables. | ||
133 | if (OutputType.Module == this.Output.Type) | ||
134 | { | ||
135 | // Gather all the suppress modularization identifiers | ||
136 | HashSet<string> suppressModularizationIdentifiers = null; | ||
137 | Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"]; | ||
138 | if (null != wixSuppressModularizationTable) | ||
139 | { | ||
140 | suppressModularizationIdentifiers = new HashSet<string>(wixSuppressModularizationTable.Rows.Select(row => (string)row[0])); | ||
141 | } | ||
142 | |||
143 | foreach (Table table in this.Output.Tables) | ||
144 | { | ||
145 | table.Modularize(modularizationGuid, suppressModularizationIdentifiers); | ||
146 | } | ||
147 | } | ||
148 | |||
149 | // This must occur after all variables and source paths have been resolved and after modularization. | ||
150 | List<FileFacade> fileFacades; | ||
151 | { | ||
152 | GetFileFacadesCommand command = new GetFileFacadesCommand(); | ||
153 | command.FileTable = this.Output.Tables["File"]; | ||
154 | command.WixFileTable = this.Output.Tables["WixFile"]; | ||
155 | command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"]; | ||
156 | command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"]; | ||
157 | command.Execute(); | ||
158 | |||
159 | fileFacades = command.FileFacades; | ||
160 | } | ||
161 | |||
162 | ////if (OutputType.Patch == this.Output.Type) | ||
163 | ////{ | ||
164 | //// foreach (SubStorage substorage in this.Output.SubStorages) | ||
165 | //// { | ||
166 | //// Output transform = substorage.Data; | ||
167 | |||
168 | //// ResolveFieldsCommand command = new ResolveFieldsCommand(); | ||
169 | //// command.Tables = transform.Tables; | ||
170 | //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
171 | //// command.FileManagerCore = this.FileManagerCore; | ||
172 | //// command.FileManagers = this.FileManagers; | ||
173 | //// command.SupportDelayedResolution = false; | ||
174 | //// command.TempFilesLocation = this.TempFilesLocation; | ||
175 | //// command.WixVariableResolver = this.WixVariableResolver; | ||
176 | //// command.Execute(); | ||
177 | |||
178 | //// this.MergeUnrealTables(transform.Tables); | ||
179 | //// } | ||
180 | ////} | ||
181 | |||
182 | { | ||
183 | CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand(); | ||
184 | command.PropertyTable = this.Output.Tables["Property"]; | ||
185 | command.WixPropertyTable = this.Output.Tables["WixProperty"]; | ||
186 | command.Execute(); | ||
187 | } | ||
188 | |||
189 | if (Messaging.Instance.EncounteredError) | ||
190 | { | ||
191 | return; | ||
192 | } | ||
193 | |||
194 | // Add binder variables for all properties. | ||
195 | Table propertyTable = this.Output.Tables["Property"]; | ||
196 | if (null != propertyTable) | ||
197 | { | ||
198 | foreach (PropertyRow propertyRow in propertyTable.Rows) | ||
199 | { | ||
200 | // Set the ProductCode if it is to be generated. | ||
201 | if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal)) | ||
202 | { | ||
203 | propertyRow.Value = Common.GenerateGuid(); | ||
204 | |||
205 | // Update the target ProductCode in any instance transforms. | ||
206 | foreach (SubStorage subStorage in this.Output.SubStorages) | ||
207 | { | ||
208 | Output subStorageOutput = subStorage.Data; | ||
209 | if (OutputType.Transform != subStorageOutput.Type) | ||
210 | { | ||
211 | continue; | ||
212 | } | ||
213 | |||
214 | Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; | ||
215 | foreach (Row row in instanceSummaryInformationTable.Rows) | ||
216 | { | ||
217 | if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) | ||
218 | { | ||
219 | row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); | ||
220 | break; | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | } | ||
225 | |||
226 | // Add the property name and value to the variableCache. | ||
227 | if (null != variableCache) | ||
228 | { | ||
229 | string key = String.Concat("property.", Common.Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property)); | ||
230 | variableCache[key] = propertyRow.Value; | ||
231 | } | ||
232 | } | ||
233 | } | ||
234 | |||
235 | // Extract files that come from cabinet files (this does not extract files from merge modules). | ||
236 | { | ||
237 | ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand(); | ||
238 | command.FilesWithEmbeddedFiles = this.ExpectedEmbeddedFiles; | ||
239 | command.Execute(); | ||
240 | } | ||
241 | |||
242 | if (OutputType.Product == this.Output.Type) | ||
243 | { | ||
244 | // Retrieve files and their information from merge modules. | ||
245 | Table wixMergeTable = this.Output.Tables["WixMerge"]; | ||
246 | |||
247 | if (null != wixMergeTable) | ||
248 | { | ||
249 | ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand(); | ||
250 | command.FileFacades = fileFacades; | ||
251 | command.FileTable = this.Output.Tables["File"]; | ||
252 | command.WixFileTable = this.Output.Tables["WixFile"]; | ||
253 | command.WixMergeTable = wixMergeTable; | ||
254 | command.OutputInstallerVersion = installerVersion; | ||
255 | command.SuppressLayout = this.SuppressLayout; | ||
256 | command.TempFilesLocation = this.IntermediateFolder; | ||
257 | command.Execute(); | ||
258 | |||
259 | fileFacades.AddRange(command.MergeModulesFileFacades); | ||
260 | } | ||
261 | } | ||
262 | else if (OutputType.Patch == this.Output.Type) | ||
263 | { | ||
264 | // Merge transform data into the output object. | ||
265 | IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output); | ||
266 | |||
267 | fileFacades.AddRange(filesFromTransform); | ||
268 | } | ||
269 | |||
270 | // stop processing if an error previously occurred | ||
271 | if (Messaging.Instance.EncounteredError) | ||
272 | { | ||
273 | return; | ||
274 | } | ||
275 | |||
276 | Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation()); | ||
277 | |||
278 | // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table). | ||
279 | { | ||
280 | UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); | ||
281 | command.FileFacades = fileFacades; | ||
282 | command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule); | ||
283 | command.ModularizationGuid = modularizationGuid; | ||
284 | command.Output = this.Output; | ||
285 | command.OverwriteHash = true; | ||
286 | command.TableDefinitions = this.TableDefinitions; | ||
287 | command.VariableCache = variableCache; | ||
288 | command.Execute(); | ||
289 | } | ||
290 | |||
291 | // Set generated component guids. | ||
292 | this.SetComponentGuids(this.Output); | ||
293 | |||
294 | // With the Component Guids set now we can create instance transforms. | ||
295 | this.CreateInstanceTransforms(this.Output); | ||
296 | |||
297 | this.ValidateComponentGuids(this.Output); | ||
298 | |||
299 | this.UpdateControlText(this.Output); | ||
300 | |||
301 | if (this.DelayedFields.Any()) | ||
302 | { | ||
303 | ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand(); | ||
304 | command.OutputType = this.Output.Type; | ||
305 | command.DelayedFields = this.DelayedFields; | ||
306 | command.ModularizationGuid = null; | ||
307 | command.VariableCache = variableCache; | ||
308 | command.Execute(); | ||
309 | } | ||
310 | |||
311 | // Assign files to media. | ||
312 | RowDictionary<MediaRow> assignedMediaRows; | ||
313 | Dictionary<MediaRow, IEnumerable<FileFacade>> filesByCabinetMedia; | ||
314 | IEnumerable<FileFacade> uncompressedFiles; | ||
315 | { | ||
316 | AssignMediaCommand command = new AssignMediaCommand(); | ||
317 | command.FilesCompressed = compressed; | ||
318 | command.FileFacades = fileFacades; | ||
319 | command.Output = this.Output; | ||
320 | command.TableDefinitions = this.TableDefinitions; | ||
321 | command.Execute(); | ||
322 | |||
323 | assignedMediaRows = command.MediaRows; | ||
324 | filesByCabinetMedia = command.FileFacadesByCabinetMedia; | ||
325 | uncompressedFiles = command.UncompressedFileFacades; | ||
326 | } | ||
327 | |||
328 | // Update file sequence. | ||
329 | this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows); | ||
330 | |||
331 | // stop processing if an error previously occurred | ||
332 | if (Messaging.Instance.EncounteredError) | ||
333 | { | ||
334 | return; | ||
335 | } | ||
336 | |||
337 | // Extended binder extensions can be called now that fields are resolved. | ||
338 | { | ||
339 | Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]); | ||
340 | |||
341 | foreach (IBinderExtension extension in this.Extensions) | ||
342 | { | ||
343 | extension.AfterResolvedFields(this.Output); | ||
344 | } | ||
345 | |||
346 | List<FileFacade> updatedFileFacades = new List<FileFacade>(); | ||
347 | |||
348 | foreach (Row updatedFile in updatedFiles.Rows) | ||
349 | { | ||
350 | string updatedId = updatedFile.FieldAsString(0); | ||
351 | |||
352 | FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId)); | ||
353 | |||
354 | updatedFileFacades.Add(updatedFacade); | ||
355 | } | ||
356 | |||
357 | if (updatedFileFacades.Any()) | ||
358 | { | ||
359 | UpdateFileFacadesCommand command = new UpdateFileFacadesCommand(); | ||
360 | command.FileFacades = fileFacades; | ||
361 | command.UpdateFileFacades = updatedFileFacades; | ||
362 | command.ModularizationGuid = modularizationGuid; | ||
363 | command.Output = this.Output; | ||
364 | command.OverwriteHash = true; | ||
365 | command.TableDefinitions = this.TableDefinitions; | ||
366 | command.VariableCache = variableCache; | ||
367 | command.Execute(); | ||
368 | } | ||
369 | } | ||
370 | |||
371 | // stop processing if an error previously occurred | ||
372 | if (Messaging.Instance.EncounteredError) | ||
373 | { | ||
374 | return; | ||
375 | } | ||
376 | |||
377 | Directory.CreateDirectory(this.IntermediateFolder); | ||
378 | |||
379 | if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch) | ||
380 | { | ||
381 | CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand(); | ||
382 | command.FileFacades = fileFacades; | ||
383 | command.WixPatchIdTable = this.Output.Tables["WixPatchId"]; | ||
384 | command.TempFilesLocation = this.IntermediateFolder; | ||
385 | command.Execute(); | ||
386 | } | ||
387 | |||
388 | // create cabinet files and process uncompressed files | ||
389 | string layoutDirectory = Path.GetDirectoryName(this.OutputPath); | ||
390 | if (!this.SuppressLayout || OutputType.Module == this.Output.Type) | ||
391 | { | ||
392 | Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles()); | ||
393 | |||
394 | var command = new CreateCabinetsCommand(); | ||
395 | command.CabbingThreadCount = this.CabbingThreadCount; | ||
396 | command.CabCachePath = this.CabCachePath; | ||
397 | command.DefaultCompressionLevel = this.DefaultCompressionLevel; | ||
398 | command.Output = this.Output; | ||
399 | command.BackendExtensions = this.BackendExtensions; | ||
400 | command.LayoutDirectory = layoutDirectory; | ||
401 | command.Compressed = compressed; | ||
402 | command.FileRowsByCabinet = filesByCabinetMedia; | ||
403 | command.ResolveMedia = this.ResolveMedia; | ||
404 | command.TableDefinitions = this.TableDefinitions; | ||
405 | command.TempFilesLocation = this.IntermediateFolder; | ||
406 | command.WixMediaTable = this.Output.Tables["WixMedia"]; | ||
407 | command.Execute(); | ||
408 | |||
409 | fileTransfers.AddRange(command.FileTransfers); | ||
410 | } | ||
411 | |||
412 | if (OutputType.Patch == this.Output.Type) | ||
413 | { | ||
414 | // copy output data back into the transforms | ||
415 | this.CopyToTransformData(this.Output); | ||
416 | } | ||
417 | |||
418 | // stop processing if an error previously occurred | ||
419 | if (Messaging.Instance.EncounteredError) | ||
420 | { | ||
421 | return; | ||
422 | } | ||
423 | |||
424 | // add back suppressed tables which must be present prior to merging in modules | ||
425 | if (OutputType.Product == this.Output.Type) | ||
426 | { | ||
427 | Table wixMergeTable = this.Output.Tables["WixMerge"]; | ||
428 | |||
429 | if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count) | ||
430 | { | ||
431 | foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) | ||
432 | { | ||
433 | string sequenceTableName = sequence.ToString(); | ||
434 | Table sequenceTable = this.Output.Tables[sequenceTableName]; | ||
435 | |||
436 | if (null == sequenceTable) | ||
437 | { | ||
438 | sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); | ||
439 | } | ||
440 | |||
441 | if (0 == sequenceTable.Rows.Count) | ||
442 | { | ||
443 | suppressedTableNames.Add(sequenceTableName); | ||
444 | } | ||
445 | } | ||
446 | } | ||
447 | } | ||
448 | |||
449 | //foreach (BinderExtension extension in this.Extensions) | ||
450 | //{ | ||
451 | // extension.PostBind(this.Context); | ||
452 | //} | ||
453 | |||
454 | // generate database file | ||
455 | Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase()); | ||
456 | string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath)); | ||
457 | this.GenerateDatabase(this.Output, tempDatabaseFile, false, false); | ||
458 | |||
459 | FileTransfer transfer; | ||
460 | if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future | ||
461 | { | ||
462 | transfer.Built = true; | ||
463 | fileTransfers.Add(transfer); | ||
464 | } | ||
465 | |||
466 | // stop processing if an error previously occurred | ||
467 | if (Messaging.Instance.EncounteredError) | ||
468 | { | ||
469 | return; | ||
470 | } | ||
471 | |||
472 | // Output the output to a file | ||
473 | Pdb pdb = new Pdb(); | ||
474 | pdb.Output = this.Output; | ||
475 | if (!String.IsNullOrEmpty(this.PdbFile)) | ||
476 | { | ||
477 | pdb.Save(this.PdbFile); | ||
478 | } | ||
479 | |||
480 | // Merge modules. | ||
481 | if (OutputType.Product == this.Output.Type) | ||
482 | { | ||
483 | Messaging.Instance.OnMessage(WixVerboses.MergingModules()); | ||
484 | |||
485 | MergeModulesCommand command = new MergeModulesCommand(); | ||
486 | command.FileFacades = fileFacades; | ||
487 | command.Output = this.Output; | ||
488 | command.OutputPath = tempDatabaseFile; | ||
489 | command.SuppressedTableNames = suppressedTableNames; | ||
490 | command.Execute(); | ||
491 | |||
492 | // stop processing if an error previously occurred | ||
493 | if (Messaging.Instance.EncounteredError) | ||
494 | { | ||
495 | return; | ||
496 | } | ||
497 | } | ||
498 | |||
499 | // inspect the MSI prior to running ICEs | ||
500 | //InspectorCore inspectorCore = new InspectorCore(); | ||
501 | //foreach (InspectorExtension inspectorExtension in this.InspectorExtensions) | ||
502 | //{ | ||
503 | // inspectorExtension.Core = inspectorCore; | ||
504 | // inspectorExtension.InspectDatabase(tempDatabaseFile, pdb); | ||
505 | |||
506 | // inspectorExtension.Core = null; // reset. | ||
507 | //} | ||
508 | |||
509 | if (Messaging.Instance.EncounteredError) | ||
510 | { | ||
511 | return; | ||
512 | } | ||
513 | |||
514 | // validate the output if there is an MSI validator | ||
515 | if (null != this.Validator) | ||
516 | { | ||
517 | Stopwatch stopwatch = Stopwatch.StartNew(); | ||
518 | |||
519 | // set the output file for source line information | ||
520 | this.Validator.Output = this.Output; | ||
521 | |||
522 | Messaging.Instance.OnMessage(WixVerboses.ValidatingDatabase()); | ||
523 | |||
524 | this.Validator.Validate(tempDatabaseFile); | ||
525 | |||
526 | stopwatch.Stop(); | ||
527 | Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); | ||
528 | |||
529 | // Stop processing if an error occurred. | ||
530 | if (Messaging.Instance.EncounteredError) | ||
531 | { | ||
532 | return; | ||
533 | } | ||
534 | } | ||
535 | |||
536 | // Process uncompressed files. | ||
537 | if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) | ||
538 | { | ||
539 | var command = new ProcessUncompressedFilesCommand(); | ||
540 | command.Compressed = compressed; | ||
541 | command.FileFacades = uncompressedFiles; | ||
542 | command.LayoutDirectory = layoutDirectory; | ||
543 | command.LongNamesInImage = longNames; | ||
544 | command.MediaRows = assignedMediaRows; | ||
545 | command.ResolveMedia = this.ResolveMedia; | ||
546 | command.DatabasePath = tempDatabaseFile; | ||
547 | command.WixMediaTable = this.Output.Tables["WixMedia"]; | ||
548 | command.Execute(); | ||
549 | |||
550 | fileTransfers.AddRange(command.FileTransfers); | ||
551 | } | ||
552 | |||
553 | this.FileTransfers = fileTransfers; | ||
554 | this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList(); | ||
555 | } | ||
556 | |||
557 | /// <summary> | ||
558 | /// Localize dialogs and controls. | ||
559 | /// </summary> | ||
560 | /// <param name="tables">The tables to localize.</param> | ||
561 | private void LocalizeUI(TableIndexedCollection tables) | ||
562 | { | ||
563 | Table dialogTable = tables["Dialog"]; | ||
564 | if (null != dialogTable) | ||
565 | { | ||
566 | foreach (Row row in dialogTable.Rows) | ||
567 | { | ||
568 | string dialog = (string)row[0]; | ||
569 | |||
570 | if (this.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl)) | ||
571 | { | ||
572 | if (CompilerConstants.IntegerNotSet != localizedControl.X) | ||
573 | { | ||
574 | row[1] = localizedControl.X; | ||
575 | } | ||
576 | |||
577 | if (CompilerConstants.IntegerNotSet != localizedControl.Y) | ||
578 | { | ||
579 | row[2] = localizedControl.Y; | ||
580 | } | ||
581 | |||
582 | if (CompilerConstants.IntegerNotSet != localizedControl.Width) | ||
583 | { | ||
584 | row[3] = localizedControl.Width; | ||
585 | } | ||
586 | |||
587 | if (CompilerConstants.IntegerNotSet != localizedControl.Height) | ||
588 | { | ||
589 | row[4] = localizedControl.Height; | ||
590 | } | ||
591 | |||
592 | row[5] = (int)row[5] | localizedControl.Attributes; | ||
593 | |||
594 | if (!String.IsNullOrEmpty(localizedControl.Text)) | ||
595 | { | ||
596 | row[6] = localizedControl.Text; | ||
597 | } | ||
598 | } | ||
599 | } | ||
600 | } | ||
601 | |||
602 | Table controlTable = tables["Control"]; | ||
603 | if (null != controlTable) | ||
604 | { | ||
605 | foreach (Row row in controlTable.Rows) | ||
606 | { | ||
607 | string dialog = (string)row[0]; | ||
608 | string control = (string)row[1]; | ||
609 | |||
610 | if (this.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl)) | ||
611 | { | ||
612 | if (CompilerConstants.IntegerNotSet != localizedControl.X) | ||
613 | { | ||
614 | row[3] = localizedControl.X.ToString(); | ||
615 | } | ||
616 | |||
617 | if (CompilerConstants.IntegerNotSet != localizedControl.Y) | ||
618 | { | ||
619 | row[4] = localizedControl.Y.ToString(); | ||
620 | } | ||
621 | |||
622 | if (CompilerConstants.IntegerNotSet != localizedControl.Width) | ||
623 | { | ||
624 | row[5] = localizedControl.Width.ToString(); | ||
625 | } | ||
626 | |||
627 | if (CompilerConstants.IntegerNotSet != localizedControl.Height) | ||
628 | { | ||
629 | row[6] = localizedControl.Height.ToString(); | ||
630 | } | ||
631 | |||
632 | row[7] = (int)row[7] | localizedControl.Attributes; | ||
633 | |||
634 | if (!String.IsNullOrEmpty(localizedControl.Text)) | ||
635 | { | ||
636 | row[9] = localizedControl.Text; | ||
637 | } | ||
638 | } | ||
639 | } | ||
640 | } | ||
641 | } | ||
642 | |||
643 | /// <summary> | ||
644 | /// Copy file data between transform substorages and the patch output object | ||
645 | /// </summary> | ||
646 | /// <param name="output">The output to bind.</param> | ||
647 | /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param> | ||
648 | private IEnumerable<FileFacade> CopyFromTransformData(Output output) | ||
649 | { | ||
650 | var command = new CopyTransformDataCommand(); | ||
651 | command.CopyOutFileRows = true; | ||
652 | command.Output = output; | ||
653 | command.TableDefinitions = this.TableDefinitions; | ||
654 | command.Execute(); | ||
655 | |||
656 | return command.FileFacades; | ||
657 | } | ||
658 | |||
659 | /// <summary> | ||
660 | /// Copy file data between transform substorages and the patch output object | ||
661 | /// </summary> | ||
662 | /// <param name="output">The output to bind.</param> | ||
663 | /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param> | ||
664 | private void CopyToTransformData(Output output) | ||
665 | { | ||
666 | var command = new CopyTransformDataCommand(); | ||
667 | command.CopyOutFileRows = false; | ||
668 | command.Output = output; | ||
669 | command.TableDefinitions = this.TableDefinitions; | ||
670 | command.Execute(); | ||
671 | } | ||
672 | |||
673 | private void UpdateMediaSequences(OutputType outputType, IEnumerable<FileFacade> fileFacades, RowDictionary<MediaRow> mediaRows) | ||
674 | { | ||
675 | // Calculate sequence numbers and media disk id layout for all file media information objects. | ||
676 | if (OutputType.Module == outputType) | ||
677 | { | ||
678 | int lastSequence = 0; | ||
679 | foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. | ||
680 | { | ||
681 | facade.File.Sequence = ++lastSequence; | ||
682 | } | ||
683 | } | ||
684 | else | ||
685 | { | ||
686 | int lastSequence = 0; | ||
687 | MediaRow mediaRow = null; | ||
688 | Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>(); | ||
689 | |||
690 | // sequence the non-patch-added files | ||
691 | foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI. | ||
692 | { | ||
693 | if (null == mediaRow) | ||
694 | { | ||
695 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
696 | if (OutputType.Patch == outputType) | ||
697 | { | ||
698 | // patch Media cannot start at zero | ||
699 | lastSequence = mediaRow.LastSequence; | ||
700 | } | ||
701 | } | ||
702 | else if (mediaRow.DiskId != facade.WixFile.DiskId) | ||
703 | { | ||
704 | mediaRow.LastSequence = lastSequence; | ||
705 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
706 | } | ||
707 | |||
708 | if (0 < facade.WixFile.PatchGroup) | ||
709 | { | ||
710 | List<FileFacade> patchGroup = patchGroups[facade.WixFile.PatchGroup]; | ||
711 | |||
712 | if (null == patchGroup) | ||
713 | { | ||
714 | patchGroup = new List<FileFacade>(); | ||
715 | patchGroups.Add(facade.WixFile.PatchGroup, patchGroup); | ||
716 | } | ||
717 | |||
718 | patchGroup.Add(facade); | ||
719 | } | ||
720 | else | ||
721 | { | ||
722 | facade.File.Sequence = ++lastSequence; | ||
723 | } | ||
724 | } | ||
725 | |||
726 | if (null != mediaRow) | ||
727 | { | ||
728 | mediaRow.LastSequence = lastSequence; | ||
729 | mediaRow = null; | ||
730 | } | ||
731 | |||
732 | // sequence the patch-added files | ||
733 | foreach (List<FileFacade> patchGroup in patchGroups.Values) | ||
734 | { | ||
735 | foreach (FileFacade facade in patchGroup) | ||
736 | { | ||
737 | if (null == mediaRow) | ||
738 | { | ||
739 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
740 | } | ||
741 | else if (mediaRow.DiskId != facade.WixFile.DiskId) | ||
742 | { | ||
743 | mediaRow.LastSequence = lastSequence; | ||
744 | mediaRow = mediaRows.Get(facade.WixFile.DiskId); | ||
745 | } | ||
746 | |||
747 | facade.File.Sequence = ++lastSequence; | ||
748 | } | ||
749 | } | ||
750 | |||
751 | if (null != mediaRow) | ||
752 | { | ||
753 | mediaRow.LastSequence = lastSequence; | ||
754 | } | ||
755 | } | ||
756 | } | ||
757 | |||
758 | /// <summary> | ||
759 | /// Set the guids for components with generatable guids. | ||
760 | /// </summary> | ||
761 | /// <param name="output">Internal representation of the database to operate on.</param> | ||
762 | private void SetComponentGuids(Output output) | ||
763 | { | ||
764 | Table componentTable = output.Tables["Component"]; | ||
765 | if (null != componentTable) | ||
766 | { | ||
767 | Hashtable registryKeyRows = null; | ||
768 | Hashtable directories = null; | ||
769 | Hashtable componentIdGenSeeds = null; | ||
770 | Dictionary<string, List<FileRow>> fileRows = null; | ||
771 | |||
772 | // find components with generatable guids | ||
773 | foreach (ComponentRow componentRow in componentTable.Rows) | ||
774 | { | ||
775 | // component guid will be generated | ||
776 | if ("*" == componentRow.Guid) | ||
777 | { | ||
778 | if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath) | ||
779 | { | ||
780 | Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers)); | ||
781 | } | ||
782 | else if (componentRow.IsRegistryKeyPath) | ||
783 | { | ||
784 | if (null == registryKeyRows) | ||
785 | { | ||
786 | Table registryTable = output.Tables["Registry"]; | ||
787 | |||
788 | registryKeyRows = new Hashtable(registryTable.Rows.Count); | ||
789 | |||
790 | foreach (Row registryRow in registryTable.Rows) | ||
791 | { | ||
792 | registryKeyRows.Add((string)registryRow[0], registryRow); | ||
793 | } | ||
794 | } | ||
795 | |||
796 | Row foundRow = registryKeyRows[componentRow.KeyPath] as Row; | ||
797 | |||
798 | string bitness = componentRow.Is64Bit ? "64" : String.Empty; | ||
799 | if (null != foundRow) | ||
800 | { | ||
801 | string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]); | ||
802 | componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant(); | ||
803 | } | ||
804 | } | ||
805 | else // must be a File KeyPath | ||
806 | { | ||
807 | // if the directory table hasn't been loaded into an indexed hash | ||
808 | // of directory ids to target names do that now. | ||
809 | if (null == directories) | ||
810 | { | ||
811 | Table directoryTable = output.Tables["Directory"]; | ||
812 | |||
813 | int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0; | ||
814 | |||
815 | directories = new Hashtable(numDirectoryTableRows); | ||
816 | |||
817 | // get the target paths for all directories | ||
818 | if (null != directoryTable) | ||
819 | { | ||
820 | foreach (Row row in directoryTable.Rows) | ||
821 | { | ||
822 | // if the directory Id already exists, we will skip it here since | ||
823 | // checking for duplicate primary keys is done later when importing tables | ||
824 | // into database | ||
825 | if (directories.ContainsKey(row[0])) | ||
826 | { | ||
827 | continue; | ||
828 | } | ||
829 | |||
830 | string targetName = Common.GetName((string)row[2], false, true); | ||
831 | directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName)); | ||
832 | } | ||
833 | } | ||
834 | } | ||
835 | |||
836 | // if the component id generation seeds have not been indexed | ||
837 | // from the WixDirectory table do that now. | ||
838 | if (null == componentIdGenSeeds) | ||
839 | { | ||
840 | Table wixDirectoryTable = output.Tables["WixDirectory"]; | ||
841 | |||
842 | int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0; | ||
843 | |||
844 | componentIdGenSeeds = new Hashtable(numWixDirectoryRows); | ||
845 | |||
846 | // if there are any WixDirectory rows, build up the Component Guid | ||
847 | // generation seeds indexed by Directory/@Id. | ||
848 | if (null != wixDirectoryTable) | ||
849 | { | ||
850 | foreach (Row row in wixDirectoryTable.Rows) | ||
851 | { | ||
852 | componentIdGenSeeds.Add(row[0], (string)row[1]); | ||
853 | } | ||
854 | } | ||
855 | } | ||
856 | |||
857 | // if the file rows have not been indexed by File.Component yet | ||
858 | // then do that now | ||
859 | if (null == fileRows) | ||
860 | { | ||
861 | Table fileTable = output.Tables["File"]; | ||
862 | |||
863 | int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0; | ||
864 | |||
865 | fileRows = new Dictionary<string, List<FileRow>>(numFileRows); | ||
866 | |||
867 | if (null != fileTable) | ||
868 | { | ||
869 | foreach (FileRow file in fileTable.Rows) | ||
870 | { | ||
871 | List<FileRow> files; | ||
872 | if (!fileRows.TryGetValue(file.Component, out files)) | ||
873 | { | ||
874 | files = new List<FileRow>(); | ||
875 | fileRows.Add(file.Component, files); | ||
876 | } | ||
877 | |||
878 | files.Add(file); | ||
879 | } | ||
880 | } | ||
881 | } | ||
882 | |||
883 | // validate component meets all the conditions to have a generated guid | ||
884 | List<FileRow> currentComponentFiles = fileRows[componentRow.Component]; | ||
885 | int numFilesInComponent = currentComponentFiles.Count; | ||
886 | string path = null; | ||
887 | |||
888 | foreach (FileRow fileRow in currentComponentFiles) | ||
889 | { | ||
890 | if (fileRow.File == componentRow.KeyPath) | ||
891 | { | ||
892 | // calculate the key file's canonical target path | ||
893 | string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true); | ||
894 | string fileName = Common.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture); | ||
895 | path = Path.Combine(directoryPath, fileName); | ||
896 | |||
897 | // find paths that are not canonicalized | ||
898 | if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || | ||
899 | path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || | ||
900 | path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || | ||
901 | path.StartsWith("TARGETDIR", StringComparison.Ordinal) || | ||
902 | path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || | ||
903 | path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) | ||
904 | { | ||
905 | Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path)); | ||
906 | } | ||
907 | |||
908 | // if component has more than one file, the key path must be versioned | ||
909 | if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) | ||
910 | { | ||
911 | Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers)); | ||
912 | } | ||
913 | } | ||
914 | else | ||
915 | { | ||
916 | // not a key path, so it must be an unversioned file if component has more than one file | ||
917 | if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) | ||
918 | { | ||
919 | Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers)); | ||
920 | } | ||
921 | } | ||
922 | } | ||
923 | |||
924 | // if the rules were followed, reward with a generated guid | ||
925 | if (!Messaging.Instance.EncounteredError) | ||
926 | { | ||
927 | componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant(); | ||
928 | } | ||
929 | } | ||
930 | } | ||
931 | } | ||
932 | } | ||
933 | } | ||
934 | |||
935 | /// <summary> | ||
936 | /// Creates instance transform substorages in the output. | ||
937 | /// </summary> | ||
938 | /// <param name="output">Output containing instance transform definitions.</param> | ||
939 | private void CreateInstanceTransforms(Output output) | ||
940 | { | ||
941 | // Create and add substorages for instance transforms. | ||
942 | Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"]; | ||
943 | if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count) | ||
944 | { | ||
945 | string targetProductCode = null; | ||
946 | string targetUpgradeCode = null; | ||
947 | string targetProductVersion = null; | ||
948 | |||
949 | Table targetSummaryInformationTable = output.Tables["_SummaryInformation"]; | ||
950 | Table targetPropertyTable = output.Tables["Property"]; | ||
951 | |||
952 | // Get the data from target database | ||
953 | foreach (Row propertyRow in targetPropertyTable.Rows) | ||
954 | { | ||
955 | if ("ProductCode" == (string)propertyRow[0]) | ||
956 | { | ||
957 | targetProductCode = (string)propertyRow[1]; | ||
958 | } | ||
959 | else if ("ProductVersion" == (string)propertyRow[0]) | ||
960 | { | ||
961 | targetProductVersion = (string)propertyRow[1]; | ||
962 | } | ||
963 | else if ("UpgradeCode" == (string)propertyRow[0]) | ||
964 | { | ||
965 | targetUpgradeCode = (string)propertyRow[1]; | ||
966 | } | ||
967 | } | ||
968 | |||
969 | // Index the Instance Component Rows. | ||
970 | Dictionary<string, ComponentRow> instanceComponentGuids = new Dictionary<string, ComponentRow>(); | ||
971 | Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"]; | ||
972 | if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count) | ||
973 | { | ||
974 | foreach (Row row in targetInstanceComponentTable.Rows) | ||
975 | { | ||
976 | // Build up all the instances, we'll get the Components rows from the real Component table. | ||
977 | instanceComponentGuids.Add((string)row[0], null); | ||
978 | } | ||
979 | |||
980 | Table targetComponentTable = output.Tables["Component"]; | ||
981 | foreach (ComponentRow componentRow in targetComponentTable.Rows) | ||
982 | { | ||
983 | string component = (string)componentRow[0]; | ||
984 | if (instanceComponentGuids.ContainsKey(component)) | ||
985 | { | ||
986 | instanceComponentGuids[component] = componentRow; | ||
987 | } | ||
988 | } | ||
989 | } | ||
990 | |||
991 | // Generate the instance transforms | ||
992 | foreach (Row instanceRow in wixInstanceTransformsTable.Rows) | ||
993 | { | ||
994 | string instanceId = (string)instanceRow[0]; | ||
995 | |||
996 | Output instanceTransform = new Output(instanceRow.SourceLineNumbers); | ||
997 | instanceTransform.Type = OutputType.Transform; | ||
998 | instanceTransform.Codepage = output.Codepage; | ||
999 | |||
1000 | Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
1001 | string targetPlatformAndLanguage = null; | ||
1002 | |||
1003 | foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows) | ||
1004 | { | ||
1005 | if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE | ||
1006 | { | ||
1007 | targetPlatformAndLanguage = (string)summaryInformationRow[1]; | ||
1008 | } | ||
1009 | |||
1010 | // Copy the row's data to the transform. | ||
1011 | Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
1012 | copyOfSummaryRow[0] = summaryInformationRow[0]; | ||
1013 | copyOfSummaryRow[1] = summaryInformationRow[1]; | ||
1014 | } | ||
1015 | |||
1016 | // Modify the appropriate properties. | ||
1017 | Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); | ||
1018 | |||
1019 | // Change the ProductCode property | ||
1020 | string productCode = (string)instanceRow[2]; | ||
1021 | if ("*" == productCode) | ||
1022 | { | ||
1023 | productCode = Common.GenerateGuid(); | ||
1024 | } | ||
1025 | |||
1026 | Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
1027 | productCodeRow.Operation = RowOperation.Modify; | ||
1028 | productCodeRow.Fields[1].Modified = true; | ||
1029 | productCodeRow[0] = "ProductCode"; | ||
1030 | productCodeRow[1] = productCode; | ||
1031 | |||
1032 | // Change the instance property | ||
1033 | Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
1034 | instanceIdRow.Operation = RowOperation.Modify; | ||
1035 | instanceIdRow.Fields[1].Modified = true; | ||
1036 | instanceIdRow[0] = (string)instanceRow[1]; | ||
1037 | instanceIdRow[1] = instanceId; | ||
1038 | |||
1039 | if (null != instanceRow[3]) | ||
1040 | { | ||
1041 | // Change the ProductName property | ||
1042 | Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
1043 | productNameRow.Operation = RowOperation.Modify; | ||
1044 | productNameRow.Fields[1].Modified = true; | ||
1045 | productNameRow[0] = "ProductName"; | ||
1046 | productNameRow[1] = (string)instanceRow[3]; | ||
1047 | } | ||
1048 | |||
1049 | if (null != instanceRow[4]) | ||
1050 | { | ||
1051 | // Change the UpgradeCode property | ||
1052 | Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); | ||
1053 | upgradeCodeRow.Operation = RowOperation.Modify; | ||
1054 | upgradeCodeRow.Fields[1].Modified = true; | ||
1055 | upgradeCodeRow[0] = "UpgradeCode"; | ||
1056 | upgradeCodeRow[1] = instanceRow[4]; | ||
1057 | |||
1058 | // Change the Upgrade table | ||
1059 | Table targetUpgradeTable = output.Tables["Upgrade"]; | ||
1060 | if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) | ||
1061 | { | ||
1062 | string upgradeId = (string)instanceRow[4]; | ||
1063 | Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); | ||
1064 | foreach (Row row in targetUpgradeTable.Rows) | ||
1065 | { | ||
1066 | // In case they are upgrading other codes to this new product, leave the ones that don't match the | ||
1067 | // Product.UpgradeCode intact. | ||
1068 | if (targetUpgradeCode == (string)row[0]) | ||
1069 | { | ||
1070 | Row upgradeRow = upgradeTable.CreateRow(null); | ||
1071 | upgradeRow.Operation = RowOperation.Add; | ||
1072 | upgradeRow.Fields[0].Modified = true; | ||
1073 | // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. | ||
1074 | // upgradeRow.Fields[0].PreviousData = (string)row[0]; | ||
1075 | |||
1076 | // Inserting a new Upgrade record with the updated UpgradeCode | ||
1077 | upgradeRow[0] = upgradeId; | ||
1078 | upgradeRow[1] = row[1]; | ||
1079 | upgradeRow[2] = row[2]; | ||
1080 | upgradeRow[3] = row[3]; | ||
1081 | upgradeRow[4] = row[4]; | ||
1082 | upgradeRow[5] = row[5]; | ||
1083 | upgradeRow[6] = row[6]; | ||
1084 | |||
1085 | // Delete the old row | ||
1086 | Row upgradeRemoveRow = upgradeTable.CreateRow(null); | ||
1087 | upgradeRemoveRow.Operation = RowOperation.Delete; | ||
1088 | upgradeRemoveRow[0] = row[0]; | ||
1089 | upgradeRemoveRow[1] = row[1]; | ||
1090 | upgradeRemoveRow[2] = row[2]; | ||
1091 | upgradeRemoveRow[3] = row[3]; | ||
1092 | upgradeRemoveRow[4] = row[4]; | ||
1093 | upgradeRemoveRow[5] = row[5]; | ||
1094 | upgradeRemoveRow[6] = row[6]; | ||
1095 | } | ||
1096 | } | ||
1097 | } | ||
1098 | } | ||
1099 | |||
1100 | // If there are instance Components generate new GUIDs for them. | ||
1101 | if (0 < instanceComponentGuids.Count) | ||
1102 | { | ||
1103 | Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); | ||
1104 | foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values) | ||
1105 | { | ||
1106 | string guid = targetComponentRow.Guid; | ||
1107 | if (!String.IsNullOrEmpty(guid)) | ||
1108 | { | ||
1109 | Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); | ||
1110 | instanceComponentRow.Operation = RowOperation.Modify; | ||
1111 | instanceComponentRow.Fields[1].Modified = true; | ||
1112 | instanceComponentRow[0] = targetComponentRow[0]; | ||
1113 | instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture); | ||
1114 | instanceComponentRow[2] = targetComponentRow[2]; | ||
1115 | instanceComponentRow[3] = targetComponentRow[3]; | ||
1116 | instanceComponentRow[4] = targetComponentRow[4]; | ||
1117 | instanceComponentRow[5] = targetComponentRow[5]; | ||
1118 | } | ||
1119 | } | ||
1120 | } | ||
1121 | |||
1122 | // Update the summary information | ||
1123 | Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count); | ||
1124 | foreach (Row row in instanceSummaryInformationTable.Rows) | ||
1125 | { | ||
1126 | summaryRows[row[0]] = row; | ||
1127 | |||
1128 | if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
1129 | { | ||
1130 | row[1] = targetPlatformAndLanguage; | ||
1131 | } | ||
1132 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
1133 | { | ||
1134 | row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); | ||
1135 | } | ||
1136 | else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) | ||
1137 | { | ||
1138 | row[1] = 0; | ||
1139 | } | ||
1140 | else if ((int)SummaryInformation.Transform.Security == (int)row[0]) | ||
1141 | { | ||
1142 | row[1] = "4"; | ||
1143 | } | ||
1144 | } | ||
1145 | |||
1146 | if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) | ||
1147 | { | ||
1148 | Row summaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
1149 | summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; | ||
1150 | summaryRow[1] = targetPlatformAndLanguage; | ||
1151 | } | ||
1152 | else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) | ||
1153 | { | ||
1154 | Row summaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
1155 | summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; | ||
1156 | summaryRow[1] = "0"; | ||
1157 | } | ||
1158 | else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) | ||
1159 | { | ||
1160 | Row summaryRow = instanceSummaryInformationTable.CreateRow(null); | ||
1161 | summaryRow[0] = (int)SummaryInformation.Transform.Security; | ||
1162 | summaryRow[1] = "4"; | ||
1163 | } | ||
1164 | |||
1165 | output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); | ||
1166 | } | ||
1167 | } | ||
1168 | } | ||
1169 | |||
1170 | /// <summary> | ||
1171 | /// Validate that there are no duplicate GUIDs in the output. | ||
1172 | /// </summary> | ||
1173 | /// <remarks> | ||
1174 | /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a | ||
1175 | /// warning, as the conditions might be mutually exclusive. | ||
1176 | /// </remarks> | ||
1177 | private void ValidateComponentGuids(Output output) | ||
1178 | { | ||
1179 | Table componentTable = output.Tables["Component"]; | ||
1180 | if (null != componentTable) | ||
1181 | { | ||
1182 | Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count); | ||
1183 | |||
1184 | foreach (ComponentRow row in componentTable.Rows) | ||
1185 | { | ||
1186 | // we don't care about unmanaged components and if there's a * GUID remaining, | ||
1187 | // there's already an error that prevented it from being replaced with a real GUID. | ||
1188 | if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid) | ||
1189 | { | ||
1190 | bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition); | ||
1191 | bool allComponentsHaveConditions = thisComponentHasCondition; | ||
1192 | |||
1193 | if (componentGuidConditions.ContainsKey(row.Guid)) | ||
1194 | { | ||
1195 | allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition; | ||
1196 | |||
1197 | if (allComponentsHaveConditions) | ||
1198 | { | ||
1199 | Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid)); | ||
1200 | } | ||
1201 | else | ||
1202 | { | ||
1203 | Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid)); | ||
1204 | } | ||
1205 | } | ||
1206 | |||
1207 | componentGuidConditions[row.Guid] = allComponentsHaveConditions; | ||
1208 | } | ||
1209 | } | ||
1210 | } | ||
1211 | } | ||
1212 | |||
1213 | /// <summary> | ||
1214 | /// Update Control and BBControl text by reading from files when necessary. | ||
1215 | /// </summary> | ||
1216 | /// <param name="output">Internal representation of the msi database to operate upon.</param> | ||
1217 | private void UpdateControlText(Output output) | ||
1218 | { | ||
1219 | UpdateControlTextCommand command = new UpdateControlTextCommand(); | ||
1220 | command.BBControlTable = output.Tables["BBControl"]; | ||
1221 | command.WixBBControlTable = output.Tables["WixBBControl"]; | ||
1222 | command.ControlTable = output.Tables["Control"]; | ||
1223 | command.WixControlTable = output.Tables["WixControl"]; | ||
1224 | command.Execute(); | ||
1225 | } | ||
1226 | |||
1227 | private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory) | ||
1228 | { | ||
1229 | string layout = null; | ||
1230 | |||
1231 | foreach (var extension in this.BackendExtensions) | ||
1232 | { | ||
1233 | layout = extension.ResolveMedia(mediaRow, mediaLayoutDirectory, layoutDirectory); | ||
1234 | if (!String.IsNullOrEmpty(layout)) | ||
1235 | { | ||
1236 | break; | ||
1237 | } | ||
1238 | } | ||
1239 | |||
1240 | // If no binder file manager resolved the layout, do the default behavior. | ||
1241 | if (String.IsNullOrEmpty(layout)) | ||
1242 | { | ||
1243 | if (String.IsNullOrEmpty(mediaLayoutDirectory)) | ||
1244 | { | ||
1245 | layout = layoutDirectory; | ||
1246 | } | ||
1247 | else if (Path.IsPathRooted(mediaLayoutDirectory)) | ||
1248 | { | ||
1249 | layout = mediaLayoutDirectory; | ||
1250 | } | ||
1251 | else | ||
1252 | { | ||
1253 | layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); | ||
1254 | } | ||
1255 | } | ||
1256 | |||
1257 | return layout; | ||
1258 | } | ||
1259 | |||
1260 | /// <summary> | ||
1261 | /// Creates the MSI/MSM/PCP database. | ||
1262 | /// </summary> | ||
1263 | /// <param name="output">Output to create database for.</param> | ||
1264 | /// <param name="databaseFile">The database file to create.</param> | ||
1265 | /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param> | ||
1266 | /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param> | ||
1267 | private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) | ||
1268 | { | ||
1269 | var command = new GenerateDatabaseCommand(); | ||
1270 | command.Extensions = this.Extensions; | ||
1271 | command.Output = output; | ||
1272 | command.OutputPath = databaseFile; | ||
1273 | command.KeepAddedColumns = keepAddedColumns; | ||
1274 | command.UseSubDirectory = useSubdirectory; | ||
1275 | command.SuppressAddingValidationRows = this.SuppressAddingValidationRows; | ||
1276 | command.TableDefinitions = this.TableDefinitions; | ||
1277 | command.TempFilesLocation = this.IntermediateFolder; | ||
1278 | command.Codepage = this.Codepage; | ||
1279 | command.Execute(); | ||
1280 | } | ||
1281 | } | ||
1282 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs new file mode 100644 index 00000000..5471792d --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs | |||
@@ -0,0 +1,135 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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 | ||
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.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs new file mode 100644 index 00000000..425d1f9c --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs | |||
@@ -0,0 +1,470 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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.Extensibility; | ||
11 | using WixToolset.Msi; | ||
12 | using WixToolset.Core.Native; | ||
13 | |||
14 | internal class BindTransformCommand | ||
15 | { | ||
16 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
17 | |||
18 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
19 | |||
20 | public string TempFilesLocation { private get; set; } | ||
21 | |||
22 | public Output Transform { private get; set; } | ||
23 | |||
24 | public string OutputPath { private get; set; } | ||
25 | |||
26 | public void Execute() | ||
27 | { | ||
28 | int transformFlags = 0; | ||
29 | |||
30 | Output targetOutput = new Output(null); | ||
31 | Output updatedOutput = new Output(null); | ||
32 | |||
33 | // TODO: handle added columns | ||
34 | |||
35 | // to generate a localized transform, both the target and updated | ||
36 | // databases need to have the same code page. the only reason to | ||
37 | // set different code pages is to support localized primary key | ||
38 | // columns, but that would only support deleting rows. if this | ||
39 | // becomes necessary, define a PreviousCodepage property on the | ||
40 | // Output class and persist this throughout transform generation. | ||
41 | targetOutput.Codepage = this.Transform.Codepage; | ||
42 | updatedOutput.Codepage = this.Transform.Codepage; | ||
43 | |||
44 | // remove certain Property rows which will be populated from summary information values | ||
45 | string targetUpgradeCode = null; | ||
46 | string updatedUpgradeCode = null; | ||
47 | |||
48 | Table propertyTable = this.Transform.Tables["Property"]; | ||
49 | if (null != propertyTable) | ||
50 | { | ||
51 | for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) | ||
52 | { | ||
53 | Row row = propertyTable.Rows[i]; | ||
54 | |||
55 | if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) | ||
56 | { | ||
57 | propertyTable.Rows.RemoveAt(i); | ||
58 | |||
59 | if ("UpgradeCode" == (string)row[0]) | ||
60 | { | ||
61 | updatedUpgradeCode = (string)row[1]; | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | |||
67 | Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
68 | Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
69 | Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
70 | Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
71 | |||
72 | // process special summary information values | ||
73 | foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows) | ||
74 | { | ||
75 | if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) | ||
76 | { | ||
77 | // convert from a web name if provided | ||
78 | string codePage = (string)row.Fields[1].Data; | ||
79 | if (null == codePage) | ||
80 | { | ||
81 | codePage = "0"; | ||
82 | } | ||
83 | else | ||
84 | { | ||
85 | codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); | ||
86 | } | ||
87 | |||
88 | string previousCodePage = (string)row.Fields[1].PreviousData; | ||
89 | if (null == previousCodePage) | ||
90 | { | ||
91 | previousCodePage = "0"; | ||
92 | } | ||
93 | else | ||
94 | { | ||
95 | previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); | ||
96 | } | ||
97 | |||
98 | Row targetCodePageRow = targetSummaryInfo.CreateRow(null); | ||
99 | targetCodePageRow[0] = 1; // PID_CODEPAGE | ||
100 | targetCodePageRow[1] = previousCodePage; | ||
101 | |||
102 | Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null); | ||
103 | updatedCodePageRow[0] = 1; // PID_CODEPAGE | ||
104 | updatedCodePageRow[1] = codePage; | ||
105 | } | ||
106 | else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] || | ||
107 | (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
108 | { | ||
109 | // the target language | ||
110 | string[] propertyData = ((string)row[1]).Split(';'); | ||
111 | string lang = 2 == propertyData.Length ? propertyData[1] : "0"; | ||
112 | |||
113 | Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo; | ||
114 | Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable; | ||
115 | |||
116 | Row productLanguageRow = tempPropertyTable.CreateRow(null); | ||
117 | productLanguageRow[0] = "ProductLanguage"; | ||
118 | productLanguageRow[1] = lang; | ||
119 | |||
120 | // set the platform;language on the MSI to be generated | ||
121 | Row templateRow = tempSummaryInfo.CreateRow(null); | ||
122 | templateRow[0] = 7; // PID_TEMPLATE | ||
123 | templateRow[1] = (string)row[1]; | ||
124 | } | ||
125 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
126 | { | ||
127 | string[] propertyData = ((string)row[1]).Split(';'); | ||
128 | |||
129 | Row targetProductCodeRow = targetPropertyTable.CreateRow(null); | ||
130 | targetProductCodeRow[0] = "ProductCode"; | ||
131 | targetProductCodeRow[1] = propertyData[0].Substring(0, 38); | ||
132 | |||
133 | Row targetProductVersionRow = targetPropertyTable.CreateRow(null); | ||
134 | targetProductVersionRow[0] = "ProductVersion"; | ||
135 | targetProductVersionRow[1] = propertyData[0].Substring(38); | ||
136 | |||
137 | Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null); | ||
138 | updatedProductCodeRow[0] = "ProductCode"; | ||
139 | updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); | ||
140 | |||
141 | Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null); | ||
142 | updatedProductVersionRow[0] = "ProductVersion"; | ||
143 | updatedProductVersionRow[1] = propertyData[1].Substring(38); | ||
144 | |||
145 | // UpgradeCode is optional and may not exists in the target | ||
146 | // or upgraded databases, so do not include a null-valued | ||
147 | // UpgradeCode property. | ||
148 | |||
149 | targetUpgradeCode = propertyData[2]; | ||
150 | if (!String.IsNullOrEmpty(targetUpgradeCode)) | ||
151 | { | ||
152 | Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); | ||
153 | targetUpgradeCodeRow[0] = "UpgradeCode"; | ||
154 | targetUpgradeCodeRow[1] = targetUpgradeCode; | ||
155 | |||
156 | // If the target UpgradeCode is specified, an updated | ||
157 | // UpgradeCode is required. | ||
158 | if (String.IsNullOrEmpty(updatedUpgradeCode)) | ||
159 | { | ||
160 | updatedUpgradeCode = targetUpgradeCode; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | if (!String.IsNullOrEmpty(updatedUpgradeCode)) | ||
165 | { | ||
166 | Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); | ||
167 | updatedUpgradeCodeRow[0] = "UpgradeCode"; | ||
168 | updatedUpgradeCodeRow[1] = updatedUpgradeCode; | ||
169 | } | ||
170 | } | ||
171 | else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) | ||
172 | { | ||
173 | transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); | ||
174 | } | ||
175 | else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0]) | ||
176 | { | ||
177 | // PID_LASTPRINTED should be null for transforms | ||
178 | row.Operation = RowOperation.None; | ||
179 | } | ||
180 | else | ||
181 | { | ||
182 | // add everything else as is | ||
183 | Row targetRow = targetSummaryInfo.CreateRow(null); | ||
184 | targetRow[0] = row[0]; | ||
185 | targetRow[1] = row[1]; | ||
186 | |||
187 | Row updatedRow = updatedSummaryInfo.CreateRow(null); | ||
188 | updatedRow[0] = row[0]; | ||
189 | updatedRow[1] = row[1]; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | // Validate that both databases have an UpgradeCode if the | ||
194 | // authoring transform will validate the UpgradeCode; otherwise, | ||
195 | // MsiCreateTransformSummaryinfo() will fail with 1620. | ||
196 | if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && | ||
197 | (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) | ||
198 | { | ||
199 | Messaging.Instance.OnMessage(WixErrors.BothUpgradeCodesRequired()); | ||
200 | } | ||
201 | |||
202 | string emptyFile = null; | ||
203 | |||
204 | foreach (Table table in this.Transform.Tables) | ||
205 | { | ||
206 | // Ignore unreal tables when building transforms except the _Stream table. | ||
207 | // These tables are ignored when generating the database so there is no reason | ||
208 | // to process them here. | ||
209 | if (table.Definition.Unreal && "_Streams" != table.Name) | ||
210 | { | ||
211 | continue; | ||
212 | } | ||
213 | |||
214 | // process table operations | ||
215 | switch (table.Operation) | ||
216 | { | ||
217 | case TableOperation.Add: | ||
218 | updatedOutput.EnsureTable(table.Definition); | ||
219 | break; | ||
220 | case TableOperation.Drop: | ||
221 | targetOutput.EnsureTable(table.Definition); | ||
222 | continue; | ||
223 | default: | ||
224 | targetOutput.EnsureTable(table.Definition); | ||
225 | updatedOutput.EnsureTable(table.Definition); | ||
226 | break; | ||
227 | } | ||
228 | |||
229 | // process row operations | ||
230 | foreach (Row row in table.Rows) | ||
231 | { | ||
232 | switch (row.Operation) | ||
233 | { | ||
234 | case RowOperation.Add: | ||
235 | Table updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
236 | updatedTable.Rows.Add(row); | ||
237 | continue; | ||
238 | case RowOperation.Delete: | ||
239 | Table targetTable = targetOutput.EnsureTable(table.Definition); | ||
240 | targetTable.Rows.Add(row); | ||
241 | |||
242 | // fill-in non-primary key values | ||
243 | foreach (Field field in row.Fields) | ||
244 | { | ||
245 | if (!field.Column.PrimaryKey) | ||
246 | { | ||
247 | if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) | ||
248 | { | ||
249 | field.Data = field.Column.MinValue; | ||
250 | } | ||
251 | else if (ColumnType.Object == field.Column.Type) | ||
252 | { | ||
253 | if (null == emptyFile) | ||
254 | { | ||
255 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
256 | } | ||
257 | |||
258 | field.Data = emptyFile; | ||
259 | } | ||
260 | else | ||
261 | { | ||
262 | field.Data = "0"; | ||
263 | } | ||
264 | } | ||
265 | } | ||
266 | continue; | ||
267 | } | ||
268 | |||
269 | // Assure that the file table's sequence is populated | ||
270 | if ("File" == table.Name) | ||
271 | { | ||
272 | foreach (Row fileRow in table.Rows) | ||
273 | { | ||
274 | if (null == fileRow[7]) | ||
275 | { | ||
276 | if (RowOperation.Add == fileRow.Operation) | ||
277 | { | ||
278 | Messaging.Instance.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); | ||
279 | break; | ||
280 | } | ||
281 | |||
282 | // Set to 1 to prevent invalid IDT file from being generated | ||
283 | fileRow[7] = 1; | ||
284 | } | ||
285 | } | ||
286 | } | ||
287 | |||
288 | // process modified and unmodified rows | ||
289 | bool modifiedRow = false; | ||
290 | Row targetRow = new Row(null, table.Definition); | ||
291 | Row updatedRow = row; | ||
292 | for (int i = 0; i < row.Fields.Length; i++) | ||
293 | { | ||
294 | Field updatedField = row.Fields[i]; | ||
295 | |||
296 | if (updatedField.Modified) | ||
297 | { | ||
298 | // set a different value in the target row to ensure this value will be modified during transform generation | ||
299 | if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) | ||
300 | { | ||
301 | if (null == updatedField.Data || 1 != (int)updatedField.Data) | ||
302 | { | ||
303 | targetRow[i] = 1; | ||
304 | } | ||
305 | else | ||
306 | { | ||
307 | targetRow[i] = 2; | ||
308 | } | ||
309 | } | ||
310 | else if (ColumnType.Object == updatedField.Column.Type) | ||
311 | { | ||
312 | if (null == emptyFile) | ||
313 | { | ||
314 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
315 | } | ||
316 | |||
317 | targetRow[i] = emptyFile; | ||
318 | } | ||
319 | else | ||
320 | { | ||
321 | if ("0" != (string)updatedField.Data) | ||
322 | { | ||
323 | targetRow[i] = "0"; | ||
324 | } | ||
325 | else | ||
326 | { | ||
327 | targetRow[i] = "1"; | ||
328 | } | ||
329 | } | ||
330 | |||
331 | modifiedRow = true; | ||
332 | } | ||
333 | else if (ColumnType.Object == updatedField.Column.Type) | ||
334 | { | ||
335 | ObjectField objectField = (ObjectField)updatedField; | ||
336 | |||
337 | // create an empty file for comparing against | ||
338 | if (null == objectField.PreviousData) | ||
339 | { | ||
340 | if (null == emptyFile) | ||
341 | { | ||
342 | emptyFile = Path.Combine(this.TempFilesLocation, "empty"); | ||
343 | } | ||
344 | |||
345 | targetRow[i] = emptyFile; | ||
346 | modifiedRow = true; | ||
347 | } | ||
348 | else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data)) | ||
349 | { | ||
350 | targetRow[i] = objectField.PreviousData; | ||
351 | modifiedRow = true; | ||
352 | } | ||
353 | } | ||
354 | else // unmodified | ||
355 | { | ||
356 | if (null != updatedField.Data) | ||
357 | { | ||
358 | targetRow[i] = updatedField.Data; | ||
359 | } | ||
360 | } | ||
361 | } | ||
362 | |||
363 | // modified rows and certain special rows go in the target and updated msi databases | ||
364 | if (modifiedRow || | ||
365 | ("Property" == table.Name && | ||
366 | ("ProductCode" == (string)row[0] || | ||
367 | "ProductLanguage" == (string)row[0] || | ||
368 | "ProductVersion" == (string)row[0] || | ||
369 | "UpgradeCode" == (string)row[0]))) | ||
370 | { | ||
371 | Table targetTable = targetOutput.EnsureTable(table.Definition); | ||
372 | targetTable.Rows.Add(targetRow); | ||
373 | |||
374 | Table updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
375 | updatedTable.Rows.Add(updatedRow); | ||
376 | } | ||
377 | } | ||
378 | } | ||
379 | |||
380 | //foreach (BinderExtension extension in this.Extensions) | ||
381 | //{ | ||
382 | // extension.PostBind(this.Context); | ||
383 | //} | ||
384 | |||
385 | // Any errors encountered up to this point can cause errors during generation. | ||
386 | if (Messaging.Instance.EncounteredError) | ||
387 | { | ||
388 | return; | ||
389 | } | ||
390 | |||
391 | string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); | ||
392 | string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi")); | ||
393 | string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi")); | ||
394 | |||
395 | try | ||
396 | { | ||
397 | if (!String.IsNullOrEmpty(emptyFile)) | ||
398 | { | ||
399 | using (FileStream fileStream = File.Create(emptyFile)) | ||
400 | { | ||
401 | } | ||
402 | } | ||
403 | |||
404 | this.GenerateDatabase(targetOutput, targetDatabaseFile, false); | ||
405 | this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true); | ||
406 | |||
407 | // make sure the directory exists | ||
408 | Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); | ||
409 | |||
410 | // create the transform file | ||
411 | using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) | ||
412 | { | ||
413 | using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) | ||
414 | { | ||
415 | if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) | ||
416 | { | ||
417 | updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); | ||
418 | } | ||
419 | else | ||
420 | { | ||
421 | Messaging.Instance.OnMessage(WixErrors.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); | ||
422 | } | ||
423 | } | ||
424 | } | ||
425 | } | ||
426 | finally | ||
427 | { | ||
428 | if (!String.IsNullOrEmpty(emptyFile)) | ||
429 | { | ||
430 | File.Delete(emptyFile); | ||
431 | } | ||
432 | } | ||
433 | } | ||
434 | |||
435 | private bool CompareFiles(string targetFile, string updatedFile) | ||
436 | { | ||
437 | bool? compared = null; | ||
438 | foreach (var extension in this.Extensions) | ||
439 | { | ||
440 | compared = extension.CompareFiles(targetFile, updatedFile); | ||
441 | if (compared.HasValue) | ||
442 | { | ||
443 | break; | ||
444 | } | ||
445 | } | ||
446 | |||
447 | if (!compared.HasValue) | ||
448 | { | ||
449 | throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. | ||
450 | } | ||
451 | |||
452 | return compared.Value; | ||
453 | } | ||
454 | |||
455 | private void GenerateDatabase(Output output, string outputPath, bool keepAddedColumns) | ||
456 | { | ||
457 | var command = new GenerateDatabaseCommand(); | ||
458 | command.Codepage = output.Codepage; | ||
459 | command.Extensions = this.Extensions; | ||
460 | command.KeepAddedColumns = keepAddedColumns; | ||
461 | command.Output = output; | ||
462 | command.OutputPath = outputPath; | ||
463 | command.TableDefinitions = this.TableDefinitions; | ||
464 | command.TempFilesLocation = this.TempFilesLocation; | ||
465 | command.SuppressAddingValidationRows = true; | ||
466 | command.UseSubDirectory = true; | ||
467 | command.Execute(); | ||
468 | } | ||
469 | } | ||
470 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs new file mode 100644 index 00000000..b2cc76fc --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs | |||
@@ -0,0 +1,177 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Databases | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.IO; | ||
8 | using System.Linq; | ||
9 | using System.Threading; | ||
10 | using WixToolset.Core.Bind; | ||
11 | using WixToolset.Core.Cab; | ||
12 | using WixToolset.Data; | ||
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 | |||
29 | public int MaximumUncompressedMediaSize { get; set; } | ||
30 | |||
31 | /// <summary> | ||
32 | /// Instantiate a new CabinetBuilder. | ||
33 | /// </summary> | ||
34 | /// <param name="threadCount">number of threads to use</param> | ||
35 | /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param> | ||
36 | public CabinetBuilder(int threadCount, IntPtr newCabNamesCallBackAddress) | ||
37 | { | ||
38 | if (0 >= threadCount) | ||
39 | { | ||
40 | throw new ArgumentOutOfRangeException("threadCount"); | ||
41 | } | ||
42 | |||
43 | this.cabinetWorkItems = new Queue(); | ||
44 | this.lockObject = new object(); | ||
45 | |||
46 | this.threadCount = threadCount; | ||
47 | |||
48 | // Set Address of Binder's callback function for Cabinet Splitting | ||
49 | this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; | ||
50 | } | ||
51 | |||
52 | /// <summary> | ||
53 | /// Enqueues a CabinetWorkItem to the queue. | ||
54 | /// </summary> | ||
55 | /// <param name="cabinetWorkItem">cabinet work item</param> | ||
56 | public void Enqueue(CabinetWorkItem cabinetWorkItem) | ||
57 | { | ||
58 | this.cabinetWorkItems.Enqueue(cabinetWorkItem); | ||
59 | } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Create the queued cabinets. | ||
63 | /// </summary> | ||
64 | /// <returns>error message number (zero if no error)</returns> | ||
65 | public void CreateQueuedCabinets() | ||
66 | { | ||
67 | // don't create more threads than the number of cabinets to build | ||
68 | if (this.cabinetWorkItems.Count < this.threadCount) | ||
69 | { | ||
70 | this.threadCount = this.cabinetWorkItems.Count; | ||
71 | } | ||
72 | |||
73 | if (0 < this.threadCount) | ||
74 | { | ||
75 | Thread[] threads = new Thread[this.threadCount]; | ||
76 | |||
77 | for (int i = 0; i < threads.Length; i++) | ||
78 | { | ||
79 | threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); | ||
80 | threads[i].Start(); | ||
81 | } | ||
82 | |||
83 | // wait for all threads to finish | ||
84 | foreach (Thread thread in threads) | ||
85 | { | ||
86 | thread.Join(); | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | |||
91 | /// <summary> | ||
92 | /// This function gets called by multiple threads to do actual work. | ||
93 | /// It takes one work item at a time and calls this.CreateCabinet(). | ||
94 | /// It does not return until cabinetWorkItems queue is empty | ||
95 | /// </summary> | ||
96 | private void ProcessWorkItems() | ||
97 | { | ||
98 | try | ||
99 | { | ||
100 | while (true) | ||
101 | { | ||
102 | CabinetWorkItem cabinetWorkItem; | ||
103 | |||
104 | lock (this.cabinetWorkItems) | ||
105 | { | ||
106 | // check if there are any more cabinets to create | ||
107 | if (0 == this.cabinetWorkItems.Count) | ||
108 | { | ||
109 | break; | ||
110 | } | ||
111 | |||
112 | cabinetWorkItem = (CabinetWorkItem)this.cabinetWorkItems.Dequeue(); | ||
113 | } | ||
114 | |||
115 | // create a cabinet | ||
116 | this.CreateCabinet(cabinetWorkItem); | ||
117 | } | ||
118 | } | ||
119 | catch (WixException we) | ||
120 | { | ||
121 | Messaging.Instance.OnMessage(we.Error); | ||
122 | } | ||
123 | catch (Exception e) | ||
124 | { | ||
125 | Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace)); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | /// <summary> | ||
130 | /// Creates a cabinet using the wixcab.dll interop layer. | ||
131 | /// </summary> | ||
132 | /// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param> | ||
133 | private void CreateCabinet(CabinetWorkItem cabinetWorkItem) | ||
134 | { | ||
135 | Messaging.Instance.OnMessage(WixVerboses.CreateCabinet(cabinetWorkItem.CabinetFile)); | ||
136 | |||
137 | int maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting | ||
138 | ulong maxPreCompressedSizeInBytes = 0; | ||
139 | |||
140 | if (MaximumCabinetSizeForLargeFileSplitting != 0) | ||
141 | { | ||
142 | // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize | ||
143 | // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file | ||
144 | if (1 == cabinetWorkItem.FileFacades.Count()) | ||
145 | { | ||
146 | // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs | ||
147 | // Get the Value for Max Uncompressed Media Size | ||
148 | maxPreCompressedSizeInBytes = (ulong)MaximumUncompressedMediaSize * 1024 * 1024; | ||
149 | |||
150 | foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row | ||
151 | { | ||
152 | if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes) | ||
153 | { | ||
154 | // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting | ||
155 | maxCabinetSize = MaximumCabinetSizeForLargeFileSplitting; | ||
156 | } | ||
157 | } | ||
158 | } | ||
159 | } | ||
160 | |||
161 | // create the cabinet file | ||
162 | string cabinetFileName = Path.GetFileName(cabinetWorkItem.CabinetFile); | ||
163 | string cabinetDirectory = Path.GetDirectoryName(cabinetWorkItem.CabinetFile); | ||
164 | |||
165 | using (WixCreateCab cab = new WixCreateCab(cabinetFileName, cabinetDirectory, cabinetWorkItem.FileFacades.Count(), maxCabinetSize, cabinetWorkItem.MaxThreshold, cabinetWorkItem.CompressionLevel)) | ||
166 | { | ||
167 | foreach (FileFacade facade in cabinetWorkItem.FileFacades) | ||
168 | { | ||
169 | cab.AddFile(facade); | ||
170 | } | ||
171 | |||
172 | cab.Complete(newCabNamesCallBackAddress); | ||
173 | } | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | |||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs new file mode 100644 index 00000000..df1ccecf --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs | |||
@@ -0,0 +1,122 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.IO; | ||
8 | using System.Linq; | ||
9 | using WixToolset.Core.Cab; | ||
10 | using WixToolset.Core.Bind; | ||
11 | using WixToolset.Data; | ||
12 | using WixToolset.Extensibility; | ||
13 | |||
14 | public class CabinetResolver | ||
15 | { | ||
16 | public CabinetResolver(string cabCachePath, IEnumerable<IWindowsInstallerBackendExtension> backendExtensions) | ||
17 | { | ||
18 | this.CabCachePath = cabCachePath; | ||
19 | |||
20 | this.BackendExtensions = backendExtensions; | ||
21 | } | ||
22 | |||
23 | private string CabCachePath { get; } | ||
24 | |||
25 | private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; } | ||
26 | |||
27 | public ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades) | ||
28 | { | ||
29 | var filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); | ||
30 | |||
31 | ResolvedCabinet resolved = null; | ||
32 | |||
33 | foreach (var extension in this.BackendExtensions) | ||
34 | { | ||
35 | resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); | ||
36 | |||
37 | if (null != resolved) | ||
38 | { | ||
39 | return resolved; | ||
40 | } | ||
41 | } | ||
42 | |||
43 | // By default cabinet should be built and moved to the suggested location. | ||
44 | resolved = new ResolvedCabinet() { BuildOption = CabinetBuildOption.BuildAndMove, Path = cabinetPath }; | ||
45 | |||
46 | // If a cabinet cache path was provided, change the location for the cabinet | ||
47 | // to be built to and check if there is a cabinet that can be reused. | ||
48 | if (!String.IsNullOrEmpty(this.CabCachePath)) | ||
49 | { | ||
50 | string cabinetName = Path.GetFileName(cabinetPath); | ||
51 | resolved.Path = Path.Combine(this.CabCachePath, cabinetName); | ||
52 | |||
53 | if (CheckFileExists(resolved.Path)) | ||
54 | { | ||
55 | // Assume that none of the following are true: | ||
56 | // 1. any files are added or removed | ||
57 | // 2. order of files changed or names changed | ||
58 | // 3. modified time changed | ||
59 | bool cabinetValid = true; | ||
60 | |||
61 | // Need to force garbage collection of WixEnumerateCab to ensure the handle | ||
62 | // associated with it is closed before it is reused. | ||
63 | using (var wixEnumerateCab = new WixEnumerateCab()) | ||
64 | { | ||
65 | List<CabinetFileInfo> fileList = wixEnumerateCab.Enumerate(resolved.Path); | ||
66 | |||
67 | if (filesWithPath.Count() != fileList.Count) | ||
68 | { | ||
69 | cabinetValid = false; | ||
70 | } | ||
71 | else | ||
72 | { | ||
73 | int i = 0; | ||
74 | foreach (BindFileWithPath file in filesWithPath) | ||
75 | { | ||
76 | // First check that the file identifiers match because that is quick and easy. | ||
77 | CabinetFileInfo cabFileInfo = fileList[i]; | ||
78 | cabinetValid = (cabFileInfo.FileId == file.Id); | ||
79 | if (cabinetValid) | ||
80 | { | ||
81 | // Still valid so ensure the file sizes are the same. | ||
82 | FileInfo fileInfo = new FileInfo(file.Path); | ||
83 | cabinetValid = (cabFileInfo.Size == fileInfo.Length); | ||
84 | if (cabinetValid) | ||
85 | { | ||
86 | // Still valid so ensure the source time stamp hasn't changed. Thus we need | ||
87 | // to convert the source file time stamp into a cabinet compatible data/time. | ||
88 | Native.CabInterop.DateTimeToCabDateAndTime(fileInfo.LastWriteTime, out var sourceCabDate, out var sourceCabTime); | ||
89 | cabinetValid = (cabFileInfo.Date == sourceCabDate && cabFileInfo.Time == sourceCabTime); | ||
90 | } | ||
91 | } | ||
92 | |||
93 | if (!cabinetValid) | ||
94 | { | ||
95 | break; | ||
96 | } | ||
97 | |||
98 | i++; | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | |||
103 | resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy; | ||
104 | } | ||
105 | } | ||
106 | |||
107 | return resolved; | ||
108 | } | ||
109 | |||
110 | private static bool CheckFileExists(string path) | ||
111 | { | ||
112 | try | ||
113 | { | ||
114 | return File.Exists(path); | ||
115 | } | ||
116 | catch (ArgumentException) | ||
117 | { | ||
118 | throw new WixException(WixErrors.IllegalCharactersInPath(path)); | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs new file mode 100644 index 00000000..dcafcd36 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs | |||
@@ -0,0 +1,79 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Databases | ||
4 | { | ||
5 | using System.Collections.Generic; | ||
6 | using WixToolset.Core.Bind; | ||
7 | using WixToolset.Data; | ||
8 | using WixToolset.Data.Rows; | ||
9 | |||
10 | /// <summary> | ||
11 | /// A cabinet builder work item. | ||
12 | /// </summary> | ||
13 | internal sealed class CabinetWorkItem | ||
14 | { | ||
15 | private string cabinetFile; | ||
16 | private CompressionLevel compressionLevel; | ||
17 | //private BinderFileManager binderFileManager; | ||
18 | private int maxThreshold; | ||
19 | |||
20 | /// <summary> | ||
21 | /// Instantiate a new CabinetWorkItem. | ||
22 | /// </summary> | ||
23 | /// <param name="fileFacades">The collection of files in this cabinet.</param> | ||
24 | /// <param name="cabinetFile">The cabinet file.</param> | ||
25 | /// <param name="maxThreshold">Maximum threshold for each cabinet.</param> | ||
26 | /// <param name="compressionLevel">The compression level of the cabinet.</param> | ||
27 | /// <param name="binderFileManager">The binder file manager.</param> | ||
28 | public CabinetWorkItem(IEnumerable<FileFacade> fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel /*, BinderFileManager binderFileManager*/) | ||
29 | { | ||
30 | this.cabinetFile = cabinetFile; | ||
31 | this.compressionLevel = compressionLevel; | ||
32 | this.FileFacades = fileFacades; | ||
33 | //this.binderFileManager = binderFileManager; | ||
34 | this.maxThreshold = maxThreshold; | ||
35 | } | ||
36 | |||
37 | /// <summary> | ||
38 | /// Gets the cabinet file. | ||
39 | /// </summary> | ||
40 | /// <value>The cabinet file.</value> | ||
41 | public string CabinetFile | ||
42 | { | ||
43 | get { return this.cabinetFile; } | ||
44 | } | ||
45 | |||
46 | /// <summary> | ||
47 | /// Gets the compression level of the cabinet. | ||
48 | /// </summary> | ||
49 | /// <value>The compression level of the cabinet.</value> | ||
50 | public CompressionLevel CompressionLevel | ||
51 | { | ||
52 | get { return this.compressionLevel; } | ||
53 | } | ||
54 | |||
55 | /// <summary> | ||
56 | /// Gets the collection of files in this cabinet. | ||
57 | /// </summary> | ||
58 | /// <value>The collection of files in this cabinet.</value> | ||
59 | public IEnumerable<FileFacade> FileFacades { get; private set; } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Gets the binder file manager. | ||
63 | /// </summary> | ||
64 | /// <value>The binder file manager.</value> | ||
65 | //public BinderFileManager BinderFileManager | ||
66 | //{ | ||
67 | // get { return this.binderFileManager; } | ||
68 | //} | ||
69 | |||
70 | /// <summary> | ||
71 | /// Gets the max threshold. | ||
72 | /// </summary> | ||
73 | /// <value>The maximum threshold for a folder in a cabinet.</value> | ||
74 | public int MaxThreshold | ||
75 | { | ||
76 | get { return this.maxThreshold; } | ||
77 | } | ||
78 | } | ||
79 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs new file mode 100644 index 00000000..d4d3799f --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs | |||
@@ -0,0 +1,91 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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.WindowsInstaller/Bind/CopyTransformDataCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs new file mode 100644 index 00000000..6388a352 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs | |||
@@ -0,0 +1,606 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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 | using WixToolset.Core.Bind; | ||
13 | |||
14 | internal class CopyTransformDataCommand | ||
15 | { | ||
16 | public bool CopyOutFileRows { private get; set; } | ||
17 | |||
18 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
19 | |||
20 | public Output Output { private get; set; } | ||
21 | |||
22 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
23 | |||
24 | public IEnumerable<FileFacade> FileFacades { get; private set; } | ||
25 | |||
26 | public void Execute() | ||
27 | { | ||
28 | Debug.Assert(OutputType.Patch != this.Output.Type); | ||
29 | |||
30 | List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null; | ||
31 | |||
32 | #if false // TODO: Fix this patching related code to work correctly with FileFacades. | ||
33 | bool copyToPatch = (allFileRows != null); | ||
34 | bool copyFromPatch = !copyToPatch; | ||
35 | |||
36 | RowDictionary<MediaRow> patchMediaRows = new RowDictionary<MediaRow>(); | ||
37 | |||
38 | Dictionary<int, RowDictionary<WixFileRow>> patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>(); | ||
39 | |||
40 | Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); | ||
41 | Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]); | ||
42 | |||
43 | if (copyFromPatch) | ||
44 | { | ||
45 | // index patch files by diskId+fileId | ||
46 | foreach (WixFileRow patchFileRow in patchFileTable.Rows) | ||
47 | { | ||
48 | int diskId = patchFileRow.DiskId; | ||
49 | RowDictionary<WixFileRow> mediaFileRows; | ||
50 | if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) | ||
51 | { | ||
52 | mediaFileRows = new RowDictionary<WixFileRow>(); | ||
53 | patchMediaFileRows.Add(diskId, mediaFileRows); | ||
54 | } | ||
55 | |||
56 | mediaFileRows.Add(patchFileRow); | ||
57 | } | ||
58 | |||
59 | Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]); | ||
60 | patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable); | ||
61 | } | ||
62 | |||
63 | // index paired transforms | ||
64 | Dictionary<string, Output> pairedTransforms = new Dictionary<string, Output>(); | ||
65 | foreach (SubStorage substorage in this.Output.SubStorages) | ||
66 | { | ||
67 | if (substorage.Name.StartsWith("#")) | ||
68 | { | ||
69 | pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | try | ||
74 | { | ||
75 | // copy File bind data into substorages | ||
76 | foreach (SubStorage substorage in this.Output.SubStorages) | ||
77 | { | ||
78 | if (substorage.Name.StartsWith("#")) | ||
79 | { | ||
80 | // no changes necessary for paired transforms | ||
81 | continue; | ||
82 | } | ||
83 | |||
84 | Output mainTransform = substorage.Data; | ||
85 | Table mainWixFileTable = mainTransform.Tables["WixFile"]; | ||
86 | Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"]; | ||
87 | |||
88 | this.FileManagerCore.ActiveSubStorage = substorage; | ||
89 | |||
90 | RowDictionary<WixFileRow> mainWixFiles = new RowDictionary<WixFileRow>(mainWixFileTable); | ||
91 | RowDictionary<Row> mainMsiFileHashIndex = new RowDictionary<Row>(); | ||
92 | |||
93 | Table mainFileTable = mainTransform.Tables["File"]; | ||
94 | Output pairedTransform = (Output)pairedTransforms[substorage.Name]; | ||
95 | |||
96 | // copy Media.LastSequence and index the MsiFileHash table if it exists. | ||
97 | if (copyFromPatch) | ||
98 | { | ||
99 | Table pairedMediaTable = pairedTransform.Tables["Media"]; | ||
100 | foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) | ||
101 | { | ||
102 | MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); | ||
103 | pairedMediaRow.Fields[1] = patchMediaRow.Fields[1]; | ||
104 | } | ||
105 | |||
106 | if (null != mainMsiFileHashTable) | ||
107 | { | ||
108 | mainMsiFileHashIndex = new RowDictionary<Row>(mainMsiFileHashTable); | ||
109 | } | ||
110 | |||
111 | // Validate file row changes for keypath-related issues | ||
112 | this.ValidateFileRowChanges(mainTransform); | ||
113 | } | ||
114 | |||
115 | // Index File table of pairedTransform | ||
116 | Table pairedFileTable = pairedTransform.Tables["File"]; | ||
117 | RowDictionary<FileRow> pairedFileRows = new RowDictionary<FileRow>(pairedFileTable); | ||
118 | |||
119 | if (null != mainFileTable) | ||
120 | { | ||
121 | if (copyFromPatch) | ||
122 | { | ||
123 | // Remove the MsiFileHash table because it will be updated later with the final file hash for each file | ||
124 | mainTransform.Tables.Remove("MsiFileHash"); | ||
125 | } | ||
126 | |||
127 | foreach (FileRow mainFileRow in mainFileTable.Rows) | ||
128 | { | ||
129 | if (RowOperation.Delete == mainFileRow.Operation) | ||
130 | { | ||
131 | continue; | ||
132 | } | ||
133 | else if (RowOperation.None == mainFileRow.Operation && !copyToPatch) | ||
134 | { | ||
135 | continue; | ||
136 | } | ||
137 | |||
138 | WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File); | ||
139 | |||
140 | if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes. | ||
141 | { | ||
142 | ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6]; | ||
143 | FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File); | ||
144 | |||
145 | // If the file is new, we always need to add it to the patch. | ||
146 | if (mainFileRow.Operation != RowOperation.Add) | ||
147 | { | ||
148 | // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. | ||
149 | if (null == objectField.PreviousData) | ||
150 | { | ||
151 | if (mainFileRow.Operation == RowOperation.None) | ||
152 | { | ||
153 | continue; | ||
154 | } | ||
155 | } | ||
156 | else | ||
157 | { | ||
158 | // TODO: should this entire condition be placed in the binder file manager? | ||
159 | if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) && | ||
160 | !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString())) | ||
161 | { | ||
162 | // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. | ||
163 | mainFileRow.Operation = RowOperation.Modify; | ||
164 | if (null != pairedFileRow) | ||
165 | { | ||
166 | // Always patch-added, but never non-compressed. | ||
167 | pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; | ||
168 | pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
169 | pairedFileRow.Fields[6].Modified = true; | ||
170 | pairedFileRow.Operation = RowOperation.Modify; | ||
171 | } | ||
172 | } | ||
173 | else | ||
174 | { | ||
175 | // The File is same. We need mark all the attributes as unchanged. | ||
176 | mainFileRow.Operation = RowOperation.None; | ||
177 | foreach (Field field in mainFileRow.Fields) | ||
178 | { | ||
179 | field.Modified = false; | ||
180 | } | ||
181 | |||
182 | if (null != pairedFileRow) | ||
183 | { | ||
184 | pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded; | ||
185 | pairedFileRow.Fields[6].Modified = false; | ||
186 | pairedFileRow.Operation = RowOperation.None; | ||
187 | } | ||
188 | continue; | ||
189 | } | ||
190 | } | ||
191 | } | ||
192 | else if (null != pairedFileRow) // RowOperation.Add | ||
193 | { | ||
194 | // Always patch-added, but never non-compressed. | ||
195 | pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; | ||
196 | pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
197 | pairedFileRow.Fields[6].Modified = true; | ||
198 | pairedFileRow.Operation = RowOperation.Add; | ||
199 | } | ||
200 | } | ||
201 | |||
202 | // index patch files by diskId+fileId | ||
203 | int diskId = mainWixFileRow.DiskId; | ||
204 | |||
205 | RowDictionary<WixFileRow> mediaFileRows; | ||
206 | if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) | ||
207 | { | ||
208 | mediaFileRows = new RowDictionary<WixFileRow>(); | ||
209 | patchMediaFileRows.Add(diskId, mediaFileRows); | ||
210 | } | ||
211 | |||
212 | string fileId = mainFileRow.File; | ||
213 | WixFileRow patchFileRow = mediaFileRows.Get(fileId); | ||
214 | if (copyToPatch) | ||
215 | { | ||
216 | if (null == patchFileRow) | ||
217 | { | ||
218 | FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
219 | patchActualFileRow.CopyFrom(mainFileRow); | ||
220 | |||
221 | patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
222 | patchFileRow.CopyFrom(mainWixFileRow); | ||
223 | |||
224 | mediaFileRows.Add(patchFileRow); | ||
225 | |||
226 | allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right? | ||
227 | } | ||
228 | else | ||
229 | { | ||
230 | // TODO: confirm the rest of data is identical? | ||
231 | |||
232 | // make sure Source is same. Otherwise we are silently ignoring a file. | ||
233 | if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase)) | ||
234 | { | ||
235 | Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source)); | ||
236 | } | ||
237 | |||
238 | // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. | ||
239 | patchFileRow.AppendPreviousDataFrom(mainWixFileRow); | ||
240 | } | ||
241 | } | ||
242 | else | ||
243 | { | ||
244 | // copy data from the patch back to the transform | ||
245 | if (null != patchFileRow) | ||
246 | { | ||
247 | FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId); | ||
248 | for (int i = 0; i < patchFileRow.Fields.Length; i++) | ||
249 | { | ||
250 | string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString(); | ||
251 | string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString(); | ||
252 | |||
253 | if (1 == i) | ||
254 | { | ||
255 | // File.Component_ changes should not come from the shared file rows | ||
256 | // that contain the file information as each individual transform might | ||
257 | // have different changes (or no changes at all). | ||
258 | } | ||
259 | // File.Attributes should not changed for binary deltas | ||
260 | else if (6 == i) | ||
261 | { | ||
262 | if (null != patchFileRow.Patch) | ||
263 | { | ||
264 | // File.Attribute should not change for binary deltas | ||
265 | pairedFileRow.Attributes = mainFileRow.Attributes; | ||
266 | mainFileRow.Fields[i].Modified = false; | ||
267 | } | ||
268 | } | ||
269 | // File.Sequence is updated in pairedTransform, not mainTransform | ||
270 | else if (7 == i) | ||
271 | { | ||
272 | // file sequence is updated in Patch table instead of File table for delta patches | ||
273 | if (null != patchFileRow.Patch) | ||
274 | { | ||
275 | pairedFileRow.Fields[i].Modified = false; | ||
276 | } | ||
277 | else | ||
278 | { | ||
279 | pairedFileRow[i] = patchFileRow[i]; | ||
280 | pairedFileRow.Fields[i].Modified = true; | ||
281 | } | ||
282 | mainFileRow.Fields[i].Modified = false; | ||
283 | } | ||
284 | else if (patchValue != mainValue) | ||
285 | { | ||
286 | mainFileRow[i] = patchFileRow[i]; | ||
287 | mainFileRow.Fields[i].Modified = true; | ||
288 | if (mainFileRow.Operation == RowOperation.None) | ||
289 | { | ||
290 | mainFileRow.Operation = RowOperation.Modify; | ||
291 | } | ||
292 | } | ||
293 | } | ||
294 | |||
295 | // copy MsiFileHash row for this File | ||
296 | Row patchHashRow; | ||
297 | if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow)) | ||
298 | { | ||
299 | patchHashRow = patchFileRow.Hash; | ||
300 | } | ||
301 | |||
302 | if (null != patchHashRow) | ||
303 | { | ||
304 | Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); | ||
305 | Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
306 | for (int i = 0; i < patchHashRow.Fields.Length; i++) | ||
307 | { | ||
308 | mainHashRow[i] = patchHashRow[i]; | ||
309 | if (i > 1) | ||
310 | { | ||
311 | // assume all hash fields have been modified | ||
312 | mainHashRow.Fields[i].Modified = true; | ||
313 | } | ||
314 | } | ||
315 | |||
316 | // assume the MsiFileHash operation follows the File one | ||
317 | mainHashRow.Operation = mainFileRow.Operation; | ||
318 | } | ||
319 | |||
320 | // copy MsiAssemblyName rows for this File | ||
321 | List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames; | ||
322 | if (null != patchAssemblyNameRows) | ||
323 | { | ||
324 | Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
325 | foreach (Row patchAssemblyNameRow in patchAssemblyNameRows) | ||
326 | { | ||
327 | // Copy if there isn't an identical modified/added row already in the transform. | ||
328 | bool foundMatchingModifiedRow = false; | ||
329 | foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows) | ||
330 | { | ||
331 | if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) | ||
332 | { | ||
333 | foundMatchingModifiedRow = true; | ||
334 | break; | ||
335 | } | ||
336 | } | ||
337 | |||
338 | if (!foundMatchingModifiedRow) | ||
339 | { | ||
340 | Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
341 | for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++) | ||
342 | { | ||
343 | mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; | ||
344 | } | ||
345 | |||
346 | // assume value field has been modified | ||
347 | mainAssemblyNameRow.Fields[2].Modified = true; | ||
348 | mainAssemblyNameRow.Operation = mainFileRow.Operation; | ||
349 | } | ||
350 | } | ||
351 | } | ||
352 | |||
353 | // Add patch header for this file | ||
354 | if (null != patchFileRow.Patch) | ||
355 | { | ||
356 | // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. | ||
357 | AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); | ||
358 | AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); | ||
359 | |||
360 | // Add to Patch table | ||
361 | Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); | ||
362 | if (0 == patchTable.Rows.Count) | ||
363 | { | ||
364 | patchTable.Operation = TableOperation.Add; | ||
365 | } | ||
366 | |||
367 | Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
368 | patchRow[0] = patchFileRow.File; | ||
369 | patchRow[1] = patchFileRow.Sequence; | ||
370 | |||
371 | FileInfo patchFile = new FileInfo(patchFileRow.Source); | ||
372 | patchRow[2] = (int)patchFile.Length; | ||
373 | patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; | ||
374 | |||
375 | string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; | ||
376 | if (MsiInterop.MsiMaxStreamNameLength < streamName.Length) | ||
377 | { | ||
378 | streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); | ||
379 | Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); | ||
380 | if (0 == patchHeadersTable.Rows.Count) | ||
381 | { | ||
382 | patchHeadersTable.Operation = TableOperation.Add; | ||
383 | } | ||
384 | Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
385 | patchHeadersRow[0] = streamName; | ||
386 | patchHeadersRow[1] = patchFileRow.Patch; | ||
387 | patchRow[5] = streamName; | ||
388 | patchHeadersRow.Operation = RowOperation.Add; | ||
389 | } | ||
390 | else | ||
391 | { | ||
392 | patchRow[4] = patchFileRow.Patch; | ||
393 | } | ||
394 | patchRow.Operation = RowOperation.Add; | ||
395 | } | ||
396 | } | ||
397 | else | ||
398 | { | ||
399 | // TODO: throw because all transform rows should have made it into the patch | ||
400 | } | ||
401 | } | ||
402 | } | ||
403 | } | ||
404 | |||
405 | if (copyFromPatch) | ||
406 | { | ||
407 | this.Output.Tables.Remove("Media"); | ||
408 | this.Output.Tables.Remove("File"); | ||
409 | this.Output.Tables.Remove("MsiFileHash"); | ||
410 | this.Output.Tables.Remove("MsiAssemblyName"); | ||
411 | } | ||
412 | } | ||
413 | } | ||
414 | finally | ||
415 | { | ||
416 | this.FileManagerCore.ActiveSubStorage = null; | ||
417 | } | ||
418 | #endif | ||
419 | this.FileFacades = allFileRows; | ||
420 | } | ||
421 | |||
422 | /// <summary> | ||
423 | /// Adds the PatchFiles action to the sequence table if it does not already exist. | ||
424 | /// </summary> | ||
425 | /// <param name="table">The sequence table to check or modify.</param> | ||
426 | /// <param name="mainTransform">The primary authoring transform.</param> | ||
427 | /// <param name="pairedTransform">The secondary patch transform.</param> | ||
428 | /// <param name="mainFileRow">The file row that contains information about the patched file.</param> | ||
429 | private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow) | ||
430 | { | ||
431 | // Find/add PatchFiles action (also determine sequence for it). | ||
432 | // Search mainTransform first, then pairedTransform (pairedTransform overrides). | ||
433 | bool hasPatchFilesAction = false; | ||
434 | int seqInstallFiles = 0; | ||
435 | int seqDuplicateFiles = 0; | ||
436 | string tableName = table.ToString(); | ||
437 | |||
438 | TestSequenceTableForPatchFilesAction( | ||
439 | mainTransform.Tables[tableName], | ||
440 | ref hasPatchFilesAction, | ||
441 | ref seqInstallFiles, | ||
442 | ref seqDuplicateFiles); | ||
443 | TestSequenceTableForPatchFilesAction( | ||
444 | pairedTransform.Tables[tableName], | ||
445 | ref hasPatchFilesAction, | ||
446 | ref seqInstallFiles, | ||
447 | ref seqDuplicateFiles); | ||
448 | if (!hasPatchFilesAction) | ||
449 | { | ||
450 | Table iesTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); | ||
451 | if (0 == iesTable.Rows.Count) | ||
452 | { | ||
453 | iesTable.Operation = TableOperation.Add; | ||
454 | } | ||
455 | |||
456 | Row patchAction = iesTable.CreateRow(null); | ||
457 | WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"]; | ||
458 | int sequence = wixPatchAction.Sequence; | ||
459 | // Test for default sequence value's appropriateness | ||
460 | if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence)) | ||
461 | { | ||
462 | if (0 != seqDuplicateFiles) | ||
463 | { | ||
464 | if (seqDuplicateFiles < seqInstallFiles) | ||
465 | { | ||
466 | throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); | ||
467 | } | ||
468 | else | ||
469 | { | ||
470 | sequence = (seqDuplicateFiles + seqInstallFiles) / 2; | ||
471 | if (seqInstallFiles == sequence || seqDuplicateFiles == sequence) | ||
472 | { | ||
473 | throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); | ||
474 | } | ||
475 | } | ||
476 | } | ||
477 | else | ||
478 | { | ||
479 | sequence = seqInstallFiles + 1; | ||
480 | } | ||
481 | } | ||
482 | patchAction[0] = wixPatchAction.Action; | ||
483 | patchAction[1] = wixPatchAction.Condition; | ||
484 | patchAction[2] = sequence; | ||
485 | patchAction.Operation = RowOperation.Add; | ||
486 | } | ||
487 | } | ||
488 | |||
489 | /// <summary> | ||
490 | /// Tests sequence table for PatchFiles and associated actions | ||
491 | /// </summary> | ||
492 | /// <param name="iesTable">The table to test.</param> | ||
493 | /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param> | ||
494 | /// <param name="seqInstallFiles">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param> | ||
495 | /// <param name="seqDuplicateFiles">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param> | ||
496 | private static void TestSequenceTableForPatchFilesAction(Table iesTable, ref bool hasPatchFilesAction, ref int seqInstallFiles, ref int seqDuplicateFiles) | ||
497 | { | ||
498 | if (null != iesTable) | ||
499 | { | ||
500 | foreach (Row iesRow in iesTable.Rows) | ||
501 | { | ||
502 | if (String.Equals("PatchFiles", (string)iesRow[0], StringComparison.Ordinal)) | ||
503 | { | ||
504 | hasPatchFilesAction = true; | ||
505 | } | ||
506 | if (String.Equals("InstallFiles", (string)iesRow[0], StringComparison.Ordinal)) | ||
507 | { | ||
508 | seqInstallFiles = (int)iesRow.Fields[2].Data; | ||
509 | } | ||
510 | if (String.Equals("DuplicateFiles", (string)iesRow[0], StringComparison.Ordinal)) | ||
511 | { | ||
512 | seqDuplicateFiles = (int)iesRow.Fields[2].Data; | ||
513 | } | ||
514 | } | ||
515 | } | ||
516 | } | ||
517 | |||
518 | /// <summary> | ||
519 | /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. | ||
520 | /// </summary> | ||
521 | /// <param name="output">The output to validate.</param> | ||
522 | private void ValidateFileRowChanges(Output transform) | ||
523 | { | ||
524 | Table componentTable = transform.Tables["Component"]; | ||
525 | Table fileTable = transform.Tables["File"]; | ||
526 | |||
527 | // There's no sense validating keypaths if the transform has no component or file table | ||
528 | if (componentTable == null || fileTable == null) | ||
529 | { | ||
530 | return; | ||
531 | } | ||
532 | |||
533 | Dictionary<string, string> componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count); | ||
534 | |||
535 | // Index the Component table for non-directory & non-registry key paths. | ||
536 | foreach (Row row in componentTable.Rows) | ||
537 | { | ||
538 | if (null != row.Fields[5].Data && | ||
539 | 0 != ((int)row.Fields[3].Data & MsiInterop.MsidbComponentAttributesRegistryKeyPath)) | ||
540 | { | ||
541 | componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString()); | ||
542 | } | ||
543 | } | ||
544 | |||
545 | Dictionary<string, string> componentWithChangedKeyPath = new Dictionary<string, string>(); | ||
546 | Dictionary<string, string> componentWithNonKeyPathChanged = new Dictionary<string, string>(); | ||
547 | // Verify changes in the file table, now that file diffing has occurred | ||
548 | foreach (FileRow row in fileTable.Rows) | ||
549 | { | ||
550 | string fileId = row.Fields[0].Data.ToString(); | ||
551 | string componentId = row.Fields[1].Data.ToString(); | ||
552 | |||
553 | if (RowOperation.Modify != row.Operation) | ||
554 | { | ||
555 | continue; | ||
556 | } | ||
557 | |||
558 | // If this file is the keypath of a component | ||
559 | if (componentKeyPath.ContainsValue(fileId)) | ||
560 | { | ||
561 | if (!componentWithChangedKeyPath.ContainsKey(componentId)) | ||
562 | { | ||
563 | componentWithChangedKeyPath.Add(componentId, fileId); | ||
564 | } | ||
565 | } | ||
566 | else | ||
567 | { | ||
568 | if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) | ||
569 | { | ||
570 | componentWithNonKeyPathChanged.Add(componentId, fileId); | ||
571 | } | ||
572 | } | ||
573 | } | ||
574 | |||
575 | foreach (KeyValuePair<string, string> componentFile in componentWithNonKeyPathChanged) | ||
576 | { | ||
577 | // Make sure all changes to non keypath files also had a change in the keypath. | ||
578 | if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key)) | ||
579 | { | ||
580 | Messaging.Instance.OnMessage(WixWarnings.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key])); | ||
581 | } | ||
582 | } | ||
583 | } | ||
584 | |||
585 | private bool CompareFiles(string targetFile, string updatedFile) | ||
586 | { | ||
587 | bool? compared = null; | ||
588 | foreach (var extension in this.Extensions) | ||
589 | { | ||
590 | compared = extension.CompareFiles(targetFile, updatedFile); | ||
591 | |||
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.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs new file mode 100644 index 00000000..02015744 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs | |||
@@ -0,0 +1,499 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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.Core.Bind; | ||
13 | using WixToolset.Core.WindowsInstaller.Bind; | ||
14 | using WixToolset.Data; | ||
15 | using WixToolset.Data.Bind; | ||
16 | using WixToolset.Data.Rows; | ||
17 | using WixToolset.Extensibility; | ||
18 | |||
19 | /// <summary> | ||
20 | /// Creates cabinet files. | ||
21 | /// </summary> | ||
22 | internal class CreateCabinetsCommand | ||
23 | { | ||
24 | public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB | ||
25 | public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) | ||
26 | |||
27 | private List<FileTransfer> fileTransfers; | ||
28 | |||
29 | private FileSplitCabNamesCallback newCabNamesCallBack; | ||
30 | |||
31 | private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence | ||
32 | |||
33 | public CreateCabinetsCommand() | ||
34 | { | ||
35 | this.fileTransfers = new List<FileTransfer>(); | ||
36 | |||
37 | this.newCabNamesCallBack = this.NewCabNamesCallBack; | ||
38 | } | ||
39 | |||
40 | /// <summary> | ||
41 | /// Sets the number of threads to use for cabinet creation. | ||
42 | /// </summary> | ||
43 | public int CabbingThreadCount { private get; set; } | ||
44 | |||
45 | public string CabCachePath { private get; set; } | ||
46 | |||
47 | public string TempFilesLocation { private get; set; } | ||
48 | |||
49 | /// <summary> | ||
50 | /// Sets the default compression level to use for cabinets | ||
51 | /// that don't have their compression level explicitly set. | ||
52 | /// </summary> | ||
53 | public CompressionLevel DefaultCompressionLevel { private get; set; } | ||
54 | |||
55 | public IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { private get; set; } | ||
56 | |||
57 | public Output Output { private get; set; } | ||
58 | |||
59 | public string LayoutDirectory { private get; set; } | ||
60 | |||
61 | public bool Compressed { private get; set; } | ||
62 | |||
63 | public Dictionary<MediaRow, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; } | ||
64 | |||
65 | public Func<MediaRow, string, string, string> ResolveMedia { private get; set; } | ||
66 | |||
67 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
68 | |||
69 | public Table WixMediaTable { private get; set; } | ||
70 | |||
71 | public IEnumerable<FileTransfer> FileTransfers => this.fileTransfers; | ||
72 | |||
73 | /// <param name="output">Output to generate image for.</param> | ||
74 | /// <param name="fileTransfers">Array of files to be transfered.</param> | ||
75 | /// <param name="layoutDirectory">The directory in which the image should be layed out.</param> | ||
76 | /// <param name="compressed">Flag if source image should be compressed.</param> | ||
77 | /// <returns>The uncompressed file rows.</returns> | ||
78 | public void Execute() | ||
79 | { | ||
80 | RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable); | ||
81 | |||
82 | this.lastCabinetAddedToMediaTable = new Dictionary<string, string>(); | ||
83 | |||
84 | this.SetCabbingThreadCount(); | ||
85 | |||
86 | // Send Binder object to Facilitate NewCabNamesCallBack Callback | ||
87 | CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); | ||
88 | |||
89 | // Supply Compile MediaTemplate Attributes to Cabinet Builder | ||
90 | int MaximumCabinetSizeForLargeFileSplitting; | ||
91 | int MaximumUncompressedMediaSize; | ||
92 | this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize); | ||
93 | cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting; | ||
94 | cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize; | ||
95 | |||
96 | foreach (var entry in this.FileRowsByCabinet) | ||
97 | { | ||
98 | MediaRow mediaRow = entry.Key; | ||
99 | IEnumerable<FileFacade> files = entry.Value; | ||
100 | CompressionLevel compressionLevel = this.DefaultCompressionLevel; | ||
101 | |||
102 | WixMediaRow wixMediaRow = null; | ||
103 | string mediaLayoutFolder = null; | ||
104 | |||
105 | if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) | ||
106 | { | ||
107 | mediaLayoutFolder = wixMediaRow.Layout; | ||
108 | |||
109 | if (wixMediaRow.CompressionLevel.HasValue) | ||
110 | { | ||
111 | compressionLevel = wixMediaRow.CompressionLevel.Value; | ||
112 | } | ||
113 | } | ||
114 | |||
115 | string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); | ||
116 | |||
117 | CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers); | ||
118 | if (null != cabinetWorkItem) | ||
119 | { | ||
120 | cabinetBuilder.Enqueue(cabinetWorkItem); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | // stop processing if an error previously occurred | ||
125 | if (Messaging.Instance.EncounteredError) | ||
126 | { | ||
127 | return; | ||
128 | } | ||
129 | |||
130 | // create queued cabinets with multiple threads | ||
131 | cabinetBuilder.CreateQueuedCabinets(); | ||
132 | if (Messaging.Instance.EncounteredError) | ||
133 | { | ||
134 | return; | ||
135 | } | ||
136 | } | ||
137 | |||
138 | /// <summary> | ||
139 | /// Sets the thead count to the number of processors if the current thread count is set to 0. | ||
140 | /// </summary> | ||
141 | /// <remarks>The thread count value must be greater than 0 otherwise and exception will be thrown.</remarks> | ||
142 | private void SetCabbingThreadCount() | ||
143 | { | ||
144 | // default the number of cabbing threads to the number of processors if it wasn't specified | ||
145 | if (0 == this.CabbingThreadCount) | ||
146 | { | ||
147 | string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"); | ||
148 | |||
149 | try | ||
150 | { | ||
151 | if (null != numberOfProcessors) | ||
152 | { | ||
153 | this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat); | ||
154 | |||
155 | if (0 >= this.CabbingThreadCount) | ||
156 | { | ||
157 | throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); | ||
158 | } | ||
159 | } | ||
160 | else // default to 1 if the environment variable is not set | ||
161 | { | ||
162 | this.CabbingThreadCount = 1; | ||
163 | } | ||
164 | |||
165 | Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); | ||
166 | } | ||
167 | catch (ArgumentException) | ||
168 | { | ||
169 | throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); | ||
170 | } | ||
171 | catch (FormatException) | ||
172 | { | ||
173 | throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors)); | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | |||
178 | |||
179 | /// <summary> | ||
180 | /// Creates a work item to create a cabinet. | ||
181 | /// </summary> | ||
182 | /// <param name="output">Output for the current database.</param> | ||
183 | /// <param name="cabinetDir">Directory to create cabinet in.</param> | ||
184 | /// <param name="mediaRow">MediaRow containing information about the cabinet.</param> | ||
185 | /// <param name="fileFacades">Collection of files in this cabinet.</param> | ||
186 | /// <param name="fileTransfers">Array of files to be transfered.</param> | ||
187 | /// <returns>created CabinetWorkItem object</returns> | ||
188 | private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers) | ||
189 | { | ||
190 | CabinetWorkItem cabinetWorkItem = null; | ||
191 | string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet); | ||
192 | |||
193 | // check for an empty cabinet | ||
194 | if (!fileFacades.Any()) | ||
195 | { | ||
196 | string cabinetName = mediaRow.Cabinet; | ||
197 | |||
198 | // remove the leading '#' from the embedded cabinet name to make the warning easier to understand | ||
199 | if (cabinetName.StartsWith("#", StringComparison.Ordinal)) | ||
200 | { | ||
201 | cabinetName = cabinetName.Substring(1); | ||
202 | } | ||
203 | |||
204 | // If building a patch, remind them to run -p for torch. | ||
205 | if (OutputType.Patch == output.Type) | ||
206 | { | ||
207 | Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true)); | ||
208 | } | ||
209 | else | ||
210 | { | ||
211 | Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName)); | ||
212 | } | ||
213 | } | ||
214 | |||
215 | var cabinetResolver = new CabinetResolver(this.CabCachePath, this.BackendExtensions); | ||
216 | |||
217 | ResolvedCabinet resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades); | ||
218 | |||
219 | // create a cabinet work item if it's not being skipped | ||
220 | if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) | ||
221 | { | ||
222 | int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet). | ||
223 | |||
224 | cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/); | ||
225 | } | ||
226 | else // reuse the cabinet from the cabinet cache. | ||
227 | { | ||
228 | Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path)); | ||
229 | |||
230 | try | ||
231 | { | ||
232 | // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The | ||
233 | // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that | ||
234 | // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from | ||
235 | // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output) | ||
236 | // causing the project to look like it perpetually needs a rebuild until all of the reused | ||
237 | // cabinets get newer timestamps. | ||
238 | File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now); | ||
239 | } | ||
240 | catch (Exception e) | ||
241 | { | ||
242 | Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message)); | ||
243 | } | ||
244 | } | ||
245 | |||
246 | if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
247 | { | ||
248 | Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]); | ||
249 | |||
250 | Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers); | ||
251 | streamRow[0] = mediaRow.Cabinet.Substring(1); | ||
252 | streamRow[1] = resolvedCabinet.Path; | ||
253 | } | ||
254 | else | ||
255 | { | ||
256 | string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet); | ||
257 | FileTransfer transfer; | ||
258 | if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer)) | ||
259 | { | ||
260 | transfer.Built = true; | ||
261 | fileTransfers.Add(transfer); | ||
262 | } | ||
263 | } | ||
264 | |||
265 | return cabinetWorkItem; | ||
266 | } | ||
267 | |||
268 | //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades) | ||
269 | //{ | ||
270 | // ResolvedCabinet resolved = null; | ||
271 | |||
272 | // List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); | ||
273 | |||
274 | // foreach (var extension in this.BackendExtensions) | ||
275 | // { | ||
276 | // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); | ||
277 | // if (null != resolved) | ||
278 | // { | ||
279 | // break; | ||
280 | // } | ||
281 | // } | ||
282 | |||
283 | // return resolved; | ||
284 | //} | ||
285 | |||
286 | /// <summary> | ||
287 | /// Delegate for Cabinet Split Callback | ||
288 | /// </summary> | ||
289 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] | ||
290 | internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken); | ||
291 | |||
292 | /// <summary> | ||
293 | /// Call back to Add File Transfer for new Cab and add new Cab to Media table | ||
294 | /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe | ||
295 | /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored | ||
296 | /// </summary> | ||
297 | /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param> | ||
298 | /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param> | ||
299 | /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param> | ||
300 | internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) | ||
301 | { | ||
302 | // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads | ||
303 | Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); | ||
304 | try | ||
305 | { | ||
306 | if (!mutex.WaitOne(0, false)) // Check if you can get the lock | ||
307 | { | ||
308 | // Cound not get the Lock | ||
309 | Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel()); | ||
310 | mutex.WaitOne(); // Wait on other thread | ||
311 | } | ||
312 | |||
313 | string firstCabinetName = firstCabName + ".cab"; | ||
314 | string newCabinetName = newCabName; | ||
315 | bool transferAdded = false; // Used for Error Handling | ||
316 | |||
317 | // Create File Transfer for new Cabinet using transfer of Base Cabinet | ||
318 | foreach (FileTransfer transfer in this.FileTransfers) | ||
319 | { | ||
320 | if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) | ||
321 | { | ||
322 | string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); | ||
323 | string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); | ||
324 | |||
325 | FileTransfer newTransfer; | ||
326 | if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer)) | ||
327 | { | ||
328 | newTransfer.Built = true; | ||
329 | this.fileTransfers.Add(newTransfer); | ||
330 | transferAdded = true; | ||
331 | break; | ||
332 | } | ||
333 | } | ||
334 | } | ||
335 | |||
336 | // Check if File Transfer was added | ||
337 | if (!transferAdded) | ||
338 | { | ||
339 | throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); | ||
340 | } | ||
341 | |||
342 | // Add the new Cabinets to media table using LastSequence of Base Cabinet | ||
343 | Table mediaTable = this.Output.Tables["Media"]; | ||
344 | Table wixFileTable = this.Output.Tables["WixFile"]; | ||
345 | int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain | ||
346 | int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain | ||
347 | bool lastSplitCabinetFound = false; // Used for Error Handling | ||
348 | |||
349 | string lastCabinetOfThisSequence = String.Empty; | ||
350 | // Get the Value of Last Cabinet Added in this split Sequence from Dictionary | ||
351 | if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) | ||
352 | { | ||
353 | // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence | ||
354 | lastCabinetOfThisSequence = firstCabinetName; | ||
355 | } | ||
356 | |||
357 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
358 | { | ||
359 | // Get details for the Last Cabinet Added in this Split Sequence | ||
360 | if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
361 | { | ||
362 | lastSequenceForLastSplitCabAdded = mediaRow.LastSequence; | ||
363 | diskIDForLastSplitCabAdded = mediaRow.DiskId; | ||
364 | lastSplitCabinetFound = true; | ||
365 | } | ||
366 | |||
367 | // Check for Name Collision for the new Cabinet added | ||
368 | if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
369 | { | ||
370 | // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row | ||
371 | throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); | ||
372 | } | ||
373 | } | ||
374 | |||
375 | // Check if the last Split Cabinet was found in the Media Table | ||
376 | if (!lastSplitCabinetFound) | ||
377 | { | ||
378 | throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence)); | ||
379 | } | ||
380 | |||
381 | // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort | ||
382 | // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with | ||
383 | // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction | ||
384 | MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null); | ||
385 | newMediaRow.Cabinet = newCabinetName; | ||
386 | newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion | ||
387 | newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded; | ||
388 | |||
389 | // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique | ||
390 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
391 | { | ||
392 | // Check if this row comes after inserted row and it is not the new cabinet inserted row | ||
393 | if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
394 | { | ||
395 | mediaRow.DiskId++; // Increment DiskID | ||
396 | } | ||
397 | } | ||
398 | |||
399 | // Now Increment DiskID for All files Rows so that they refer to the right Media Row | ||
400 | foreach (WixFileRow wixFileRow in wixFileTable.Rows) | ||
401 | { | ||
402 | // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet | ||
403 | // This check will work as we have only one large file in every splitting cabinet | ||
404 | // If we want to support splitting cabinet with more large files we need to update this code | ||
405 | if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase)) | ||
406 | { | ||
407 | wixFileRow.DiskId++; // Increment DiskID | ||
408 | } | ||
409 | } | ||
410 | |||
411 | // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback | ||
412 | this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName; | ||
413 | |||
414 | mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails | ||
415 | } | ||
416 | finally | ||
417 | { | ||
418 | // Releasing the Mutex here | ||
419 | mutex.ReleaseMutex(); | ||
420 | } | ||
421 | } | ||
422 | |||
423 | |||
424 | /// <summary> | ||
425 | /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides | ||
426 | /// </summary> | ||
427 | /// <param name="output">Output to generate image for.</param> | ||
428 | /// <param name="fileRows">The indexed file rows.</param> | ||
429 | private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) | ||
430 | { | ||
431 | // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size | ||
432 | string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); | ||
433 | string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); | ||
434 | int maxCabSizeForLargeFileInMB = 0; | ||
435 | int maxPreCompressedSizeInMB = 0; | ||
436 | ulong testOverFlow = 0; | ||
437 | |||
438 | // Supply Compile MediaTemplate Attributes to Cabinet Builder | ||
439 | Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; | ||
440 | if (mediaTemplateTable != null) | ||
441 | { | ||
442 | WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; | ||
443 | |||
444 | // Get the Value for Max Cab Size for File Splitting | ||
445 | try | ||
446 | { | ||
447 | // Override authored mcslfs value if environment variable is authored. | ||
448 | if (!String.IsNullOrEmpty(mcslfsString)) | ||
449 | { | ||
450 | maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString); | ||
451 | } | ||
452 | else | ||
453 | { | ||
454 | maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting; | ||
455 | } | ||
456 | testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; | ||
457 | } | ||
458 | catch (FormatException) | ||
459 | { | ||
460 | throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); | ||
461 | } | ||
462 | catch (OverflowException) | ||
463 | { | ||
464 | throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting)); | ||
465 | } | ||
466 | |||
467 | try | ||
468 | { | ||
469 | // Override authored mums value if environment variable is authored. | ||
470 | if (!String.IsNullOrEmpty(mumsString)) | ||
471 | { | ||
472 | maxPreCompressedSizeInMB = Int32.Parse(mumsString); | ||
473 | } | ||
474 | else | ||
475 | { | ||
476 | maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; | ||
477 | } | ||
478 | testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; | ||
479 | } | ||
480 | catch (FormatException) | ||
481 | { | ||
482 | throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); | ||
483 | } | ||
484 | catch (OverflowException) | ||
485 | { | ||
486 | throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); | ||
487 | } | ||
488 | |||
489 | maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; | ||
490 | maxUncompressedMediaSize = maxPreCompressedSizeInMB; | ||
491 | } | ||
492 | else | ||
493 | { | ||
494 | maxCabSizeForLargeFileSplitting = 0; | ||
495 | maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; | ||
496 | } | ||
497 | } | ||
498 | } | ||
499 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs new file mode 100644 index 00000000..767671b8 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs | |||
@@ -0,0 +1,87 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Databases | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Globalization; | ||
8 | using System.IO; | ||
9 | using WixToolset.Core.Bind; | ||
10 | using WixToolset.Data; | ||
11 | using WixToolset.Data.Rows; | ||
12 | |||
13 | /// <summary> | ||
14 | /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. | ||
15 | /// </summary> | ||
16 | internal class CreateDeltaPatchesCommand | ||
17 | { | ||
18 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
19 | |||
20 | public Table WixPatchIdTable { private get; set; } | ||
21 | |||
22 | public string TempFilesLocation { private get; set; } | ||
23 | |||
24 | public void Execute() | ||
25 | { | ||
26 | bool optimizePatchSizeForLargeFiles = false; | ||
27 | PatchSymbolFlagsType apiPatchingSymbolFlags = 0; | ||
28 | |||
29 | if (null != this.WixPatchIdTable) | ||
30 | { | ||
31 | Row row = this.WixPatchIdTable.Rows[0]; | ||
32 | if (null != row) | ||
33 | { | ||
34 | if (null != row[2]) | ||
35 | { | ||
36 | optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture)); | ||
37 | } | ||
38 | |||
39 | if (null != row[3]) | ||
40 | { | ||
41 | apiPatchingSymbolFlags = (PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture); | ||
42 | } | ||
43 | } | ||
44 | } | ||
45 | |||
46 | foreach (FileFacade facade in this.FileFacades) | ||
47 | { | ||
48 | if (RowOperation.Modify == facade.File.Operation && | ||
49 | 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile)) | ||
50 | { | ||
51 | string deltaBase = String.Concat("delta_", facade.File.File); | ||
52 | string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf")); | ||
53 | string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd")); | ||
54 | |||
55 | bool retainRangeWarning = false; | ||
56 | |||
57 | if (PatchAPI.PatchInterop.CreateDelta( | ||
58 | deltaFile, | ||
59 | facade.WixFile.Source, | ||
60 | facade.DeltaPatchFile.Symbols, | ||
61 | facade.DeltaPatchFile.RetainOffsets, | ||
62 | new[] { facade.WixFile.PreviousSource }, | ||
63 | facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }), | ||
64 | facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }), | ||
65 | facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }), | ||
66 | facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }), | ||
67 | facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }), | ||
68 | apiPatchingSymbolFlags, | ||
69 | optimizePatchSizeForLargeFiles, | ||
70 | out retainRangeWarning)) | ||
71 | { | ||
72 | PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile); | ||
73 | |||
74 | facade.WixFile.Source = deltaFile; | ||
75 | facade.WixFile.DeltaPatchHeaderSource = headerFile; | ||
76 | } | ||
77 | |||
78 | if (retainRangeWarning) | ||
79 | { | ||
80 | // TODO: get patch family to add to warning message for PatchWiz parity. | ||
81 | Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File)); | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | } | ||
87 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs new file mode 100644 index 00000000..aef130b0 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs | |||
@@ -0,0 +1,68 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Databases | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using WixToolset.Data; | ||
8 | using WixToolset.Data.Rows; | ||
9 | |||
10 | internal class CreateSpecialPropertiesCommand | ||
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.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs new file mode 100644 index 00000000..ae76037d --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs | |||
@@ -0,0 +1,226 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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.Data; | ||
13 | using WixToolset.Data.Rows; | ||
14 | using WixToolset.MergeMod; | ||
15 | using WixToolset.Msi; | ||
16 | using WixToolset.Core.Native; | ||
17 | using WixToolset.Core.Bind; | ||
18 | using WixToolset.Core.Cab; | ||
19 | |||
20 | /// <summary> | ||
21 | /// Retrieve files information and extract them from merge modules. | ||
22 | /// </summary> | ||
23 | internal class ExtractMergeModuleFilesCommand | ||
24 | { | ||
25 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
26 | |||
27 | public Table FileTable { private get; set; } | ||
28 | |||
29 | public Table WixFileTable { private get; set; } | ||
30 | |||
31 | public Table WixMergeTable { private get; set; } | ||
32 | |||
33 | public int OutputInstallerVersion { private get; set; } | ||
34 | |||
35 | public bool SuppressLayout { private get; set; } | ||
36 | |||
37 | public string TempFilesLocation { private get; set; } | ||
38 | |||
39 | public IEnumerable<FileFacade> MergeModulesFileFacades { get; private set; } | ||
40 | |||
41 | public void Execute() | ||
42 | { | ||
43 | List<FileFacade> mergeModulesFileFacades = new List<FileFacade>(); | ||
44 | |||
45 | IMsmMerge2 merge = MsmInterop.GetMsmMerge(); | ||
46 | |||
47 | // Index all of the file rows to be able to detect collisions with files in the Merge Modules. | ||
48 | // It may seem a bit expensive to build up this index solely for the purpose of checking collisions | ||
49 | // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out | ||
50 | // there are other cases where we need all the file rows indexed, however they are not common cases. | ||
51 | // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let | ||
52 | // this case be slightly more expensive because the cost of maintaining an indexed file row collection | ||
53 | // is a lot more costly for the common cases. | ||
54 | Dictionary<string, FileFacade> indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal); | ||
55 | |||
56 | foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows) | ||
57 | { | ||
58 | bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); | ||
59 | |||
60 | // If the module has files and creating layout | ||
61 | if (containsFiles && !this.SuppressLayout) | ||
62 | { | ||
63 | this.ExtractFilesFromMergeModule(merge, wixMergeRow); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | this.MergeModulesFileFacades = mergeModulesFileFacades; | ||
68 | } | ||
69 | |||
70 | private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades) | ||
71 | { | ||
72 | bool containsFiles = false; | ||
73 | |||
74 | try | ||
75 | { | ||
76 | // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. | ||
77 | using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) | ||
78 | { | ||
79 | if (db.TableExists("File") && db.TableExists("Component")) | ||
80 | { | ||
81 | Dictionary<string, FileFacade> uniqueModuleFileIdentifiers = new Dictionary<string, FileFacade>(StringComparer.OrdinalIgnoreCase); | ||
82 | |||
83 | using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) | ||
84 | { | ||
85 | // add each file row from the merge module into the file row collection (check for errors along the way) | ||
86 | while (true) | ||
87 | { | ||
88 | using (Record record = view.Fetch()) | ||
89 | { | ||
90 | if (null == record) | ||
91 | { | ||
92 | break; | ||
93 | } | ||
94 | |||
95 | // NOTE: this is very tricky - the merge module file rows are not added to the | ||
96 | // file table because they should not be created via idt import. Instead, these | ||
97 | // rows are created by merging in the actual modules. | ||
98 | FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); | ||
99 | fileRow.File = record[1]; | ||
100 | fileRow.Compressed = wixMergeRow.FileCompression; | ||
101 | |||
102 | WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); | ||
103 | wixFileRow.Directory = record[2]; | ||
104 | wixFileRow.DiskId = wixMergeRow.DiskId; | ||
105 | wixFileRow.PatchGroup = -1; | ||
106 | wixFileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), Path.DirectorySeparatorChar, record[1]); | ||
107 | |||
108 | FileFacade mergeModuleFileFacade = new FileFacade(true, fileRow, wixFileRow); | ||
109 | |||
110 | FileFacade collidingFacade; | ||
111 | |||
112 | // If case-sensitive collision with another merge module or a user-authored file identifier. | ||
113 | if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) | ||
114 | { | ||
115 | Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File)); | ||
116 | } | ||
117 | else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module | ||
118 | { | ||
119 | Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File)); | ||
120 | } | ||
121 | else // no collision | ||
122 | { | ||
123 | mergeModulesFileFacades.Add(mergeModuleFileFacade); | ||
124 | |||
125 | // Keep updating the indexes as new rows are added. | ||
126 | indexedFileFacades.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); | ||
127 | uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade); | ||
128 | } | ||
129 | |||
130 | containsFiles = true; | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | |||
136 | // Get the summary information to detect the Schema | ||
137 | using (SummaryInformation summaryInformation = new SummaryInformation(db)) | ||
138 | { | ||
139 | string moduleInstallerVersionString = summaryInformation.GetProperty(14); | ||
140 | |||
141 | try | ||
142 | { | ||
143 | int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); | ||
144 | if (moduleInstallerVersion > this.OutputInstallerVersion) | ||
145 | { | ||
146 | Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion)); | ||
147 | } | ||
148 | } | ||
149 | catch (FormatException) | ||
150 | { | ||
151 | throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | catch (FileNotFoundException) | ||
157 | { | ||
158 | throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); | ||
159 | } | ||
160 | catch (Win32Exception) | ||
161 | { | ||
162 | throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile)); | ||
163 | } | ||
164 | |||
165 | return containsFiles; | ||
166 | } | ||
167 | |||
168 | private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow) | ||
169 | { | ||
170 | bool moduleOpen = false; | ||
171 | short mergeLanguage; | ||
172 | |||
173 | try | ||
174 | { | ||
175 | mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); | ||
176 | } | ||
177 | catch (System.FormatException) | ||
178 | { | ||
179 | Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); | ||
180 | return; | ||
181 | } | ||
182 | |||
183 | try | ||
184 | { | ||
185 | merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); | ||
186 | moduleOpen = true; | ||
187 | |||
188 | string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat); | ||
189 | |||
190 | // extract the module cabinet, then explode all of the files to a temp directory | ||
191 | string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab"); | ||
192 | merge.ExtractCAB(moduleCabPath); | ||
193 | |||
194 | string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId); | ||
195 | Directory.CreateDirectory(mergeIdPath); | ||
196 | |||
197 | using (var extractCab = new WixExtractCab()) | ||
198 | { | ||
199 | try | ||
200 | { | ||
201 | extractCab.Extract(moduleCabPath, mergeIdPath); | ||
202 | } | ||
203 | catch (FileNotFoundException) | ||
204 | { | ||
205 | throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); | ||
206 | } | ||
207 | catch | ||
208 | { | ||
209 | throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); | ||
210 | } | ||
211 | } | ||
212 | } | ||
213 | catch (COMException ce) | ||
214 | { | ||
215 | throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); | ||
216 | } | ||
217 | finally | ||
218 | { | ||
219 | if (moduleOpen) | ||
220 | { | ||
221 | merge.CloseModule(); | ||
222 | } | ||
223 | } | ||
224 | } | ||
225 | } | ||
226 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs new file mode 100644 index 00000000..26d254f2 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs | |||
@@ -0,0 +1,332 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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.Text; | ||
11 | using WixToolset.Data; | ||
12 | using WixToolset.Extensibility; | ||
13 | using WixToolset.Msi; | ||
14 | using WixToolset.Core.Native; | ||
15 | |||
16 | internal class GenerateDatabaseCommand | ||
17 | { | ||
18 | public int Codepage { private get; set; } | ||
19 | |||
20 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
21 | |||
22 | /// <summary> | ||
23 | /// Whether to keep columns added in a transform. | ||
24 | /// </summary> | ||
25 | public bool KeepAddedColumns { private get; set; } | ||
26 | |||
27 | public Output Output { private get; set; } | ||
28 | |||
29 | public string OutputPath { private get; set; } | ||
30 | |||
31 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
32 | |||
33 | public string TempFilesLocation { private get; set; } | ||
34 | |||
35 | /// <summary> | ||
36 | /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files. | ||
37 | /// </summary> | ||
38 | public bool SuppressAddingValidationRows { private get; set; } | ||
39 | |||
40 | public bool UseSubDirectory { private get; set; } | ||
41 | |||
42 | public void Execute() | ||
43 | { | ||
44 | // Add the _Validation rows. | ||
45 | if (!this.SuppressAddingValidationRows) | ||
46 | { | ||
47 | Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]); | ||
48 | |||
49 | foreach (Table table in this.Output.Tables) | ||
50 | { | ||
51 | if (!table.Definition.Unreal) | ||
52 | { | ||
53 | // Add the validation rows for this table. | ||
54 | table.Definition.AddValidationRows(validationTable); | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | |||
59 | // Set the base directory. | ||
60 | string baseDirectory = this.TempFilesLocation; | ||
61 | |||
62 | if (this.UseSubDirectory) | ||
63 | { | ||
64 | string filename = Path.GetFileNameWithoutExtension(this.OutputPath); | ||
65 | baseDirectory = Path.Combine(baseDirectory, filename); | ||
66 | |||
67 | // make sure the directory exists | ||
68 | Directory.CreateDirectory(baseDirectory); | ||
69 | } | ||
70 | |||
71 | try | ||
72 | { | ||
73 | OpenDatabase type = OpenDatabase.CreateDirect; | ||
74 | |||
75 | // set special flag for patch files | ||
76 | if (OutputType.Patch == this.Output.Type) | ||
77 | { | ||
78 | type |= OpenDatabase.OpenPatchFile; | ||
79 | } | ||
80 | |||
81 | #if DEBUG | ||
82 | Console.WriteLine("Opening database at: {0}", this.OutputPath); | ||
83 | #endif | ||
84 | |||
85 | using (Database db = new Database(this.OutputPath, type)) | ||
86 | { | ||
87 | // Localize the codepage if a value was specified directly. | ||
88 | if (-1 != this.Codepage) | ||
89 | { | ||
90 | this.Output.Codepage = this.Codepage; | ||
91 | } | ||
92 | |||
93 | // if we're not using the default codepage, import a new one into our | ||
94 | // database before we add any tables (or the tables would be added | ||
95 | // with the wrong codepage). | ||
96 | if (0 != this.Output.Codepage) | ||
97 | { | ||
98 | this.SetDatabaseCodepage(db, this.Output.Codepage); | ||
99 | } | ||
100 | |||
101 | foreach (Table table in this.Output.Tables) | ||
102 | { | ||
103 | Table importTable = table; | ||
104 | bool hasBinaryColumn = false; | ||
105 | |||
106 | // Skip all unreal tables other than _Streams. | ||
107 | if (table.Definition.Unreal && "_Streams" != table.Name) | ||
108 | { | ||
109 | continue; | ||
110 | } | ||
111 | |||
112 | // Do not put the _Validation table in patches, it is not needed. | ||
113 | if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name) | ||
114 | { | ||
115 | continue; | ||
116 | } | ||
117 | |||
118 | // The only way to import binary data is to copy it to a local subdirectory first. | ||
119 | // To avoid this extra copying and perf hit, import an empty table with the same | ||
120 | // definition and later import the binary data from source using records. | ||
121 | foreach (ColumnDefinition columnDefinition in table.Definition.Columns) | ||
122 | { | ||
123 | if (ColumnType.Object == columnDefinition.Type) | ||
124 | { | ||
125 | importTable = new Table(table.Section, table.Definition); | ||
126 | hasBinaryColumn = true; | ||
127 | break; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | // Create the table via IDT import. | ||
132 | if ("_Streams" != importTable.Name) | ||
133 | { | ||
134 | try | ||
135 | { | ||
136 | db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns); | ||
137 | } | ||
138 | catch (WixInvalidIdtException) | ||
139 | { | ||
140 | // If ValidateRows finds anything it doesn't like, it throws | ||
141 | importTable.ValidateRows(); | ||
142 | |||
143 | // Otherwise we rethrow the InvalidIdt | ||
144 | throw; | ||
145 | } | ||
146 | } | ||
147 | |||
148 | // insert the rows via SQL query if this table contains object fields | ||
149 | if (hasBinaryColumn) | ||
150 | { | ||
151 | StringBuilder query = new StringBuilder("SELECT "); | ||
152 | |||
153 | // Build the query for the view. | ||
154 | bool firstColumn = true; | ||
155 | foreach (ColumnDefinition columnDefinition in table.Definition.Columns) | ||
156 | { | ||
157 | if (!firstColumn) | ||
158 | { | ||
159 | query.Append(","); | ||
160 | } | ||
161 | |||
162 | query.AppendFormat(" `{0}`", columnDefinition.Name); | ||
163 | firstColumn = false; | ||
164 | } | ||
165 | query.AppendFormat(" FROM `{0}`", table.Name); | ||
166 | |||
167 | using (View tableView = db.OpenExecuteView(query.ToString())) | ||
168 | { | ||
169 | // Import each row containing a stream | ||
170 | foreach (Row row in table.Rows) | ||
171 | { | ||
172 | using (Record record = new Record(table.Definition.Columns.Count)) | ||
173 | { | ||
174 | StringBuilder streamName = new StringBuilder(); | ||
175 | bool needStream = false; | ||
176 | |||
177 | // the _Streams table doesn't prepend the table name (or a period) | ||
178 | if ("_Streams" != table.Name) | ||
179 | { | ||
180 | streamName.Append(table.Name); | ||
181 | } | ||
182 | |||
183 | for (int i = 0; i < table.Definition.Columns.Count; i++) | ||
184 | { | ||
185 | ColumnDefinition columnDefinition = table.Definition.Columns[i]; | ||
186 | |||
187 | switch (columnDefinition.Type) | ||
188 | { | ||
189 | case ColumnType.Localized: | ||
190 | case ColumnType.Preserved: | ||
191 | case ColumnType.String: | ||
192 | if (columnDefinition.PrimaryKey) | ||
193 | { | ||
194 | if (0 < streamName.Length) | ||
195 | { | ||
196 | streamName.Append("."); | ||
197 | } | ||
198 | streamName.Append((string)row[i]); | ||
199 | } | ||
200 | |||
201 | record.SetString(i + 1, (string)row[i]); | ||
202 | break; | ||
203 | case ColumnType.Number: | ||
204 | record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture)); | ||
205 | break; | ||
206 | case ColumnType.Object: | ||
207 | if (null != row[i]) | ||
208 | { | ||
209 | needStream = true; | ||
210 | try | ||
211 | { | ||
212 | record.SetStream(i + 1, (string)row[i]); | ||
213 | } | ||
214 | catch (Win32Exception e) | ||
215 | { | ||
216 | if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME | ||
217 | { | ||
218 | throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i])); | ||
219 | } | ||
220 | else | ||
221 | { | ||
222 | throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); | ||
223 | } | ||
224 | } | ||
225 | } | ||
226 | break; | ||
227 | } | ||
228 | } | ||
229 | |||
230 | // stream names are created by concatenating the name of the table with the values | ||
231 | // of the primary key (delimited by periods) | ||
232 | // check for a stream name that is more than 62 characters long (the maximum allowed length) | ||
233 | if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length) | ||
234 | { | ||
235 | Messaging.Instance.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); | ||
236 | } | ||
237 | else // add the row to the database | ||
238 | { | ||
239 | tableView.Modify(ModifyView.Assign, record); | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | } | ||
244 | |||
245 | // Remove rows from the _Streams table for wixpdbs. | ||
246 | if ("_Streams" == table.Name) | ||
247 | { | ||
248 | table.Rows.Clear(); | ||
249 | } | ||
250 | } | ||
251 | } | ||
252 | |||
253 | // Insert substorages (usually transforms inside a patch or instance transforms in a package). | ||
254 | if (0 < this.Output.SubStorages.Count) | ||
255 | { | ||
256 | using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) | ||
257 | { | ||
258 | foreach (SubStorage subStorage in this.Output.SubStorages) | ||
259 | { | ||
260 | string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst")); | ||
261 | |||
262 | // Bind the transform. | ||
263 | this.BindTransform(subStorage.Data, transformFile); | ||
264 | |||
265 | if (Messaging.Instance.EncounteredError) | ||
266 | { | ||
267 | continue; | ||
268 | } | ||
269 | |||
270 | // add the storage | ||
271 | using (Record record = new Record(2)) | ||
272 | { | ||
273 | record.SetString(1, subStorage.Name); | ||
274 | record.SetStream(2, transformFile); | ||
275 | storagesView.Modify(ModifyView.Assign, record); | ||
276 | } | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | |||
281 | // We're good, commit the changes to the new database. | ||
282 | db.Commit(); | ||
283 | } | ||
284 | } | ||
285 | catch (IOException) | ||
286 | { | ||
287 | // TODO: this error message doesn't seem specific enough | ||
288 | throw new WixFileNotFoundException(new SourceLineNumber(this.OutputPath), this.OutputPath); | ||
289 | } | ||
290 | } | ||
291 | |||
292 | private void BindTransform(Output transform, string outputPath) | ||
293 | { | ||
294 | BindTransformCommand command = new BindTransformCommand(); | ||
295 | command.Extensions = this.Extensions; | ||
296 | command.TempFilesLocation = this.TempFilesLocation; | ||
297 | command.Transform = transform; | ||
298 | command.OutputPath = outputPath; | ||
299 | command.TableDefinitions = this.TableDefinitions; | ||
300 | command.Execute(); | ||
301 | } | ||
302 | |||
303 | /// <summary> | ||
304 | /// Sets the codepage of a database. | ||
305 | /// </summary> | ||
306 | /// <param name="db">Database to set codepage into.</param> | ||
307 | /// <param name="output">Output with the codepage for the database.</param> | ||
308 | private void SetDatabaseCodepage(Database db, int codepage) | ||
309 | { | ||
310 | // write out the _ForceCodepage IDT file | ||
311 | string idtPath = Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"); | ||
312 | using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) | ||
313 | { | ||
314 | idtFile.WriteLine(); // dummy column name record | ||
315 | idtFile.WriteLine(); // dummy column definition record | ||
316 | idtFile.Write(codepage); | ||
317 | idtFile.WriteLine("\t_ForceCodepage"); | ||
318 | } | ||
319 | |||
320 | // try to import the table into the MSI | ||
321 | try | ||
322 | { | ||
323 | db.Import(idtPath); | ||
324 | } | ||
325 | catch (WixInvalidIdtException) | ||
326 | { | ||
327 | // the IDT should be valid, so an invalid code page was given | ||
328 | throw new WixException(WixErrors.IllegalCodepage(codepage)); | ||
329 | } | ||
330 | } | ||
331 | } | ||
332 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs new file mode 100644 index 00000000..caf8b7a7 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs | |||
@@ -0,0 +1,149 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Databases | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Globalization; | ||
8 | using System.Linq; | ||
9 | using WixToolset.Core.Bind; | ||
10 | using WixToolset.Data; | ||
11 | using WixToolset.Data.Rows; | ||
12 | |||
13 | internal class GetFileFacadesCommand | ||
14 | { | ||
15 | public Table FileTable { private get; set; } | ||
16 | |||
17 | public Table WixFileTable { private get; set; } | ||
18 | |||
19 | public Table WixDeltaPatchFileTable { private get; set; } | ||
20 | |||
21 | public Table WixDeltaPatchSymbolPathsTable { private get; set; } | ||
22 | |||
23 | public List<FileFacade> FileFacades { get; private set; } | ||
24 | |||
25 | public void Execute() | ||
26 | { | ||
27 | List<FileFacade> facades = new List<FileFacade>(this.FileTable.Rows.Count); | ||
28 | |||
29 | RowDictionary<WixFileRow> wixFiles = new RowDictionary<WixFileRow>(this.WixFileTable); | ||
30 | RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles = new RowDictionary<WixDeltaPatchFileRow>(this.WixDeltaPatchFileTable); | ||
31 | |||
32 | foreach (FileRow file in this.FileTable.Rows) | ||
33 | { | ||
34 | WixDeltaPatchFileRow deltaPatchFile = null; | ||
35 | |||
36 | deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile); | ||
37 | |||
38 | facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile)); | ||
39 | } | ||
40 | |||
41 | if (null != this.WixDeltaPatchSymbolPathsTable) | ||
42 | { | ||
43 | this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); | ||
44 | } | ||
45 | |||
46 | this.FileFacades = facades; | ||
47 | } | ||
48 | |||
49 | /// <summary> | ||
50 | /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. | ||
51 | /// </summary> | ||
52 | public RowDictionary<WixDeltaPatchFileRow> ResolveDeltaPatchSymbolPaths(RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles, IEnumerable<FileFacade> facades) | ||
53 | { | ||
54 | ILookup<string, FileFacade> filesByComponent = null; | ||
55 | ILookup<string, FileFacade> filesByDirectory = null; | ||
56 | ILookup<string, FileFacade> filesByDiskId = null; | ||
57 | |||
58 | foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs<WixDeltaPatchSymbolPathsRow>().OrderBy(r => r.Type)) | ||
59 | { | ||
60 | switch (row.Type) | ||
61 | { | ||
62 | case SymbolPathType.File: | ||
63 | this.MergeSymbolPaths(row, deltaPatchFiles[row.Id]); | ||
64 | break; | ||
65 | |||
66 | case SymbolPathType.Component: | ||
67 | if (null == filesByComponent) | ||
68 | { | ||
69 | filesByComponent = facades.ToLookup(f => f.File.Component); | ||
70 | } | ||
71 | |||
72 | foreach (FileFacade facade in filesByComponent[row.Id]) | ||
73 | { | ||
74 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); | ||
75 | } | ||
76 | break; | ||
77 | |||
78 | case SymbolPathType.Directory: | ||
79 | if (null == filesByDirectory) | ||
80 | { | ||
81 | filesByDirectory = facades.ToLookup(f => f.WixFile.Directory); | ||
82 | } | ||
83 | |||
84 | foreach (FileFacade facade in filesByDirectory[row.Id]) | ||
85 | { | ||
86 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); | ||
87 | } | ||
88 | break; | ||
89 | |||
90 | case SymbolPathType.Media: | ||
91 | if (null == filesByDiskId) | ||
92 | { | ||
93 | filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture)); | ||
94 | } | ||
95 | |||
96 | foreach (FileFacade facade in filesByDiskId[row.Id]) | ||
97 | { | ||
98 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); | ||
99 | } | ||
100 | break; | ||
101 | |||
102 | case SymbolPathType.Product: | ||
103 | foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values) | ||
104 | { | ||
105 | this.MergeSymbolPaths(row, fileRow); | ||
106 | } | ||
107 | break; | ||
108 | |||
109 | default: | ||
110 | // error | ||
111 | break; | ||
112 | } | ||
113 | } | ||
114 | |||
115 | return deltaPatchFiles; | ||
116 | } | ||
117 | |||
118 | /// <summary> | ||
119 | /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row. | ||
120 | /// </summary> | ||
121 | /// <param name="row">Row from the WixPatchSymbolsPaths table.</param> | ||
122 | /// <param name="file">FileRow into which to set symbol information.</param> | ||
123 | /// <comment>This includes PreviousData as well.</comment> | ||
124 | private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file) | ||
125 | { | ||
126 | if (null == file.Symbols) | ||
127 | { | ||
128 | file.Symbols = row.SymbolPaths; | ||
129 | } | ||
130 | else | ||
131 | { | ||
132 | file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths); | ||
133 | } | ||
134 | |||
135 | Field field = row.Fields[2]; | ||
136 | if (null != field.PreviousData) | ||
137 | { | ||
138 | if (null == file.PreviousSymbols) | ||
139 | { | ||
140 | file.PreviousSymbols = field.PreviousData; | ||
141 | } | ||
142 | else | ||
143 | { | ||
144 | file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | } | ||
149 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs new file mode 100644 index 00000000..624cbb43 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs | |||
@@ -0,0 +1,351 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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 | using WixToolset.Core.Bind; | ||
24 | |||
25 | /// <summary> | ||
26 | /// Update file information. | ||
27 | /// </summary> | ||
28 | internal class MergeModulesCommand | ||
29 | { | ||
30 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
31 | |||
32 | public Output Output { private get; set; } | ||
33 | |||
34 | public string OutputPath { private get; set; } | ||
35 | |||
36 | public IEnumerable<string> SuppressedTableNames { private get; set; } | ||
37 | |||
38 | public string TempFilesLocation { private get; set; } | ||
39 | |||
40 | public void Execute() | ||
41 | { | ||
42 | Debug.Assert(OutputType.Product == this.Output.Type); | ||
43 | |||
44 | Table wixMergeTable = this.Output.Tables["WixMerge"]; | ||
45 | Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"]; | ||
46 | |||
47 | // check for merge rows to see if there is any work to do | ||
48 | if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count) | ||
49 | { | ||
50 | return; | ||
51 | } | ||
52 | |||
53 | IMsmMerge2 merge = null; | ||
54 | bool commit = true; | ||
55 | bool logOpen = false; | ||
56 | bool databaseOpen = false; | ||
57 | string logPath = null; | ||
58 | try | ||
59 | { | ||
60 | merge = MsmInterop.GetMsmMerge(); | ||
61 | |||
62 | logPath = Path.Combine(this.TempFilesLocation, "merge.log"); | ||
63 | merge.OpenLog(logPath); | ||
64 | logOpen = true; | ||
65 | |||
66 | merge.OpenDatabase(this.OutputPath); | ||
67 | databaseOpen = true; | ||
68 | |||
69 | // process all the merge rows | ||
70 | foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows) | ||
71 | { | ||
72 | bool moduleOpen = false; | ||
73 | |||
74 | try | ||
75 | { | ||
76 | short mergeLanguage; | ||
77 | |||
78 | try | ||
79 | { | ||
80 | mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); | ||
81 | } | ||
82 | catch (System.FormatException) | ||
83 | { | ||
84 | Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); | ||
85 | continue; | ||
86 | } | ||
87 | |||
88 | Messaging.Instance.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); | ||
89 | merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); | ||
90 | moduleOpen = true; | ||
91 | |||
92 | // If there is merge configuration data, create a callback object to contain it all. | ||
93 | ConfigurationCallback callback = null; | ||
94 | if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData)) | ||
95 | { | ||
96 | callback = new ConfigurationCallback(wixMergeRow.ConfigurationData); | ||
97 | } | ||
98 | |||
99 | // merge the module into the database that's being built | ||
100 | Messaging.Instance.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile)); | ||
101 | merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback); | ||
102 | |||
103 | // connect any non-primary features | ||
104 | if (null != wixFeatureModulesTable) | ||
105 | { | ||
106 | foreach (Row row in wixFeatureModulesTable.Rows) | ||
107 | { | ||
108 | if (wixMergeRow.Id == (string)row[1]) | ||
109 | { | ||
110 | Messaging.Instance.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0])); | ||
111 | merge.Connect((string)row[0]); | ||
112 | } | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | catch (COMException) | ||
117 | { | ||
118 | commit = false; | ||
119 | } | ||
120 | finally | ||
121 | { | ||
122 | IMsmErrors mergeErrors = merge.Errors; | ||
123 | |||
124 | // display all the errors encountered during the merge operations for this module | ||
125 | for (int i = 1; i <= mergeErrors.Count; i++) | ||
126 | { | ||
127 | IMsmError mergeError = mergeErrors[i]; | ||
128 | StringBuilder databaseKeys = new StringBuilder(); | ||
129 | StringBuilder moduleKeys = new StringBuilder(); | ||
130 | |||
131 | // build a string of the database keys | ||
132 | for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++) | ||
133 | { | ||
134 | if (1 != j) | ||
135 | { | ||
136 | databaseKeys.Append(';'); | ||
137 | } | ||
138 | databaseKeys.Append(mergeError.DatabaseKeys[j]); | ||
139 | } | ||
140 | |||
141 | // build a string of the module keys | ||
142 | for (int j = 1; j <= mergeError.ModuleKeys.Count; j++) | ||
143 | { | ||
144 | if (1 != j) | ||
145 | { | ||
146 | moduleKeys.Append(';'); | ||
147 | } | ||
148 | moduleKeys.Append(mergeError.ModuleKeys[j]); | ||
149 | } | ||
150 | |||
151 | // display the merge error based on the msm error type | ||
152 | switch (mergeError.Type) | ||
153 | { | ||
154 | case MsmErrorType.msmErrorExclusion: | ||
155 | Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString())); | ||
156 | break; | ||
157 | case MsmErrorType.msmErrorFeatureRequired: | ||
158 | Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id)); | ||
159 | break; | ||
160 | case MsmErrorType.msmErrorLanguageFailed: | ||
161 | Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); | ||
162 | break; | ||
163 | case MsmErrorType.msmErrorLanguageUnsupported: | ||
164 | Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); | ||
165 | break; | ||
166 | case MsmErrorType.msmErrorResequenceMerge: | ||
167 | Messaging.Instance.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); | ||
168 | break; | ||
169 | case MsmErrorType.msmErrorTableMerge: | ||
170 | if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table | ||
171 | { | ||
172 | Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); | ||
173 | } | ||
174 | break; | ||
175 | case MsmErrorType.msmErrorPlatformMismatch: | ||
176 | Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); | ||
177 | break; | ||
178 | default: | ||
179 | Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); | ||
180 | break; | ||
181 | } | ||
182 | } | ||
183 | |||
184 | if (0 >= mergeErrors.Count && !commit) | ||
185 | { | ||
186 | Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace)); | ||
187 | } | ||
188 | |||
189 | if (moduleOpen) | ||
190 | { | ||
191 | merge.CloseModule(); | ||
192 | } | ||
193 | } | ||
194 | } | ||
195 | } | ||
196 | finally | ||
197 | { | ||
198 | if (databaseOpen) | ||
199 | { | ||
200 | merge.CloseDatabase(commit); | ||
201 | } | ||
202 | |||
203 | if (logOpen) | ||
204 | { | ||
205 | merge.CloseLog(); | ||
206 | } | ||
207 | } | ||
208 | |||
209 | // stop processing if an error previously occurred | ||
210 | if (Messaging.Instance.EncounteredError) | ||
211 | { | ||
212 | return; | ||
213 | } | ||
214 | |||
215 | using (Database db = new Database(this.OutputPath, OpenDatabase.Direct)) | ||
216 | { | ||
217 | Table suppressActionTable = this.Output.Tables["WixSuppressAction"]; | ||
218 | |||
219 | // suppress individual actions | ||
220 | if (null != suppressActionTable) | ||
221 | { | ||
222 | foreach (Row row in suppressActionTable.Rows) | ||
223 | { | ||
224 | if (db.TableExists((string)row[0])) | ||
225 | { | ||
226 | string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); | ||
227 | |||
228 | using (View view = db.OpenExecuteView(query)) | ||
229 | { | ||
230 | using (Record record = view.Fetch()) | ||
231 | { | ||
232 | if (null != record) | ||
233 | { | ||
234 | Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); | ||
235 | view.Modify(ModifyView.Delete, record); | ||
236 | } | ||
237 | } | ||
238 | } | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | |||
243 | // query for merge module actions in suppressed sequences and drop them | ||
244 | foreach (string tableName in this.SuppressedTableNames) | ||
245 | { | ||
246 | if (!db.TableExists(tableName)) | ||
247 | { | ||
248 | continue; | ||
249 | } | ||
250 | |||
251 | using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) | ||
252 | { | ||
253 | while (true) | ||
254 | { | ||
255 | using (Record resultRecord = view.Fetch()) | ||
256 | { | ||
257 | if (null == resultRecord) | ||
258 | { | ||
259 | break; | ||
260 | } | ||
261 | |||
262 | Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); | ||
263 | } | ||
264 | } | ||
265 | } | ||
266 | |||
267 | // drop suppressed sequences | ||
268 | using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) | ||
269 | { | ||
270 | } | ||
271 | |||
272 | // delete the validation rows | ||
273 | using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) | ||
274 | { | ||
275 | using (Record record = new Record(1)) | ||
276 | { | ||
277 | record.SetString(1, tableName); | ||
278 | view.Execute(record); | ||
279 | } | ||
280 | } | ||
281 | } | ||
282 | |||
283 | // now update the Attributes column for the files from the Merge Modules | ||
284 | Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); | ||
285 | using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) | ||
286 | { | ||
287 | foreach (FileFacade file in this.FileFacades) | ||
288 | { | ||
289 | if (!file.FromModule) | ||
290 | { | ||
291 | continue; | ||
292 | } | ||
293 | |||
294 | using (Record record = new Record(1)) | ||
295 | { | ||
296 | record.SetString(1, file.File.File); | ||
297 | view.Execute(record); | ||
298 | } | ||
299 | |||
300 | using (Record recordUpdate = view.Fetch()) | ||
301 | { | ||
302 | if (null == recordUpdate) | ||
303 | { | ||
304 | throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module."); | ||
305 | } | ||
306 | |||
307 | recordUpdate.SetInteger(1, file.File.Sequence); | ||
308 | |||
309 | // update the file attributes to match the compression specified | ||
310 | // on the Merge element or on the Package element | ||
311 | int attributes = 0; | ||
312 | |||
313 | // get the current value if its not null | ||
314 | if (!recordUpdate.IsNull(2)) | ||
315 | { | ||
316 | attributes = recordUpdate.GetInteger(2); | ||
317 | } | ||
318 | |||
319 | if (YesNoType.Yes == file.File.Compressed) | ||
320 | { | ||
321 | // these are mutually exclusive | ||
322 | attributes |= MsiInterop.MsidbFileAttributesCompressed; | ||
323 | attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
324 | } | ||
325 | else if (YesNoType.No == file.File.Compressed) | ||
326 | { | ||
327 | // these are mutually exclusive | ||
328 | attributes |= MsiInterop.MsidbFileAttributesNoncompressed; | ||
329 | attributes &= ~MsiInterop.MsidbFileAttributesCompressed; | ||
330 | } | ||
331 | else // not specified | ||
332 | { | ||
333 | Debug.Assert(YesNoType.NotSet == file.File.Compressed); | ||
334 | |||
335 | // clear any compression bits | ||
336 | attributes &= ~MsiInterop.MsidbFileAttributesCompressed; | ||
337 | attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; | ||
338 | } | ||
339 | |||
340 | recordUpdate.SetInteger(2, attributes); | ||
341 | |||
342 | view.Modify(ModifyView.Update, recordUpdate); | ||
343 | } | ||
344 | } | ||
345 | } | ||
346 | |||
347 | db.Commit(); | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs new file mode 100644 index 00000000..b3c09b9e --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs | |||
@@ -0,0 +1,118 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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 | using WixToolset.Bind; | ||
14 | using WixToolset.Core.Bind; | ||
15 | using WixToolset.Data.Bind; | ||
16 | |||
17 | /// <summary> | ||
18 | /// Defines the file transfers necessary to layout the uncompressed files. | ||
19 | /// </summary> | ||
20 | internal class ProcessUncompressedFilesCommand | ||
21 | { | ||
22 | public string DatabasePath { private get; set; } | ||
23 | |||
24 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
25 | |||
26 | public RowDictionary<MediaRow> MediaRows { private get; set; } | ||
27 | |||
28 | public string LayoutDirectory { private get; set; } | ||
29 | |||
30 | public bool Compressed { private get; set; } | ||
31 | |||
32 | public bool LongNamesInImage { private get; set; } | ||
33 | |||
34 | public Func<MediaRow, string, string, string> ResolveMedia { private get; set; } | ||
35 | |||
36 | public Table WixMediaTable { private get; set; } | ||
37 | |||
38 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
39 | |||
40 | public void Execute() | ||
41 | { | ||
42 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
43 | |||
44 | Hashtable directories = new Hashtable(); | ||
45 | |||
46 | RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable); | ||
47 | |||
48 | using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) | ||
49 | { | ||
50 | using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) | ||
51 | { | ||
52 | while (true) | ||
53 | { | ||
54 | using (Record directoryRecord = directoryView.Fetch()) | ||
55 | { | ||
56 | if (null == directoryRecord) | ||
57 | { | ||
58 | break; | ||
59 | } | ||
60 | |||
61 | string sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage); | ||
62 | |||
63 | directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); | ||
64 | } | ||
65 | } | ||
66 | } | ||
67 | |||
68 | using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) | ||
69 | { | ||
70 | using (Record fileQueryRecord = new Record(1)) | ||
71 | { | ||
72 | // for each file in the array of uncompressed files | ||
73 | foreach (FileFacade facade in this.FileFacades) | ||
74 | { | ||
75 | MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId); | ||
76 | string relativeFileLayoutPath = null; | ||
77 | |||
78 | WixMediaRow wixMediaRow = null; | ||
79 | string mediaLayoutFolder = null; | ||
80 | |||
81 | if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) | ||
82 | { | ||
83 | mediaLayoutFolder = wixMediaRow.Layout; | ||
84 | } | ||
85 | |||
86 | string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); | ||
87 | |||
88 | // setup up the query record and find the appropriate file in the | ||
89 | // previously executed file view | ||
90 | fileQueryRecord[1] = facade.File.File; | ||
91 | fileView.Execute(fileQueryRecord); | ||
92 | |||
93 | using (Record fileRecord = fileView.Fetch()) | ||
94 | { | ||
95 | if (null == fileRecord) | ||
96 | { | ||
97 | throw new WixException(WixErrors.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File)); | ||
98 | } | ||
99 | |||
100 | relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); | ||
101 | } | ||
102 | |||
103 | // finally put together the base media layout path and the relative file layout path | ||
104 | string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); | ||
105 | FileTransfer transfer; | ||
106 | if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer)) | ||
107 | { | ||
108 | fileTransfers.Add(transfer); | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | |||
115 | this.FileTransfers = fileTransfers; | ||
116 | } | ||
117 | } | ||
118 | } | ||
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs new file mode 100644 index 00000000..7da32206 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs | |||
@@ -0,0 +1,80 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Databases | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using WixToolset.Data; | ||
8 | using WixToolset.Data.Rows; | ||
9 | |||
10 | internal class UpdateControlTextCommand | ||
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.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs new file mode 100644 index 00000000..cd9444ee --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs | |||
@@ -0,0 +1,533 @@ | |||
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 | |||
3 | namespace WixToolset.Core.WindowsInstaller.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.Core.Bind; | ||
16 | using WixToolset.Data; | ||
17 | using WixToolset.Data.Rows; | ||
18 | using WixToolset.Msi; | ||
19 | |||
20 | /// <summary> | ||
21 | /// Update file information. | ||
22 | /// </summary> | ||
23 | internal class UpdateFileFacadesCommand | ||
24 | { | ||
25 | public IEnumerable<FileFacade> FileFacades { private get; set; } | ||
26 | |||
27 | public IEnumerable<FileFacade> UpdateFileFacades { private get; set; } | ||
28 | |||
29 | public string ModularizationGuid { private get; set; } | ||
30 | |||
31 | public Output Output { private get; set; } | ||
32 | |||
33 | public bool OverwriteHash { private get; set; } | ||
34 | |||
35 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
36 | |||
37 | public IDictionary<string, string> VariableCache { private get; set; } | ||
38 | |||
39 | public void Execute() | ||
40 | { | ||
41 | foreach (FileFacade file in this.UpdateFileFacades) | ||
42 | { | ||
43 | this.UpdateFileFacade(file); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | private void UpdateFileFacade(FileFacade file) | ||
48 | { | ||
49 | FileInfo fileInfo = null; | ||
50 | try | ||
51 | { | ||
52 | fileInfo = new FileInfo(file.WixFile.Source); | ||
53 | } | ||
54 | catch (ArgumentException) | ||
55 | { | ||
56 | Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
57 | return; | ||
58 | } | ||
59 | catch (PathTooLongException) | ||
60 | { | ||
61 | Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
62 | return; | ||
63 | } | ||
64 | catch (NotSupportedException) | ||
65 | { | ||
66 | Messaging.Instance.OnMessage(WixDataErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
67 | return; | ||
68 | } | ||
69 | |||
70 | if (!fileInfo.Exists) | ||
71 | { | ||
72 | Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source)); | ||
73 | return; | ||
74 | } | ||
75 | |||
76 | using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) | ||
77 | { | ||
78 | if (Int32.MaxValue < fileStream.Length) | ||
79 | { | ||
80 | throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source)); | ||
81 | } | ||
82 | |||
83 | file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); | ||
84 | } | ||
85 | |||
86 | string version = null; | ||
87 | string language = null; | ||
88 | try | ||
89 | { | ||
90 | Installer.GetFileVersion(fileInfo.FullName, out version, out language); | ||
91 | } | ||
92 | catch (Win32Exception e) | ||
93 | { | ||
94 | if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND | ||
95 | { | ||
96 | throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); | ||
97 | } | ||
98 | else | ||
99 | { | ||
100 | throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); | ||
101 | } | ||
102 | } | ||
103 | |||
104 | // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. | ||
105 | if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table | ||
106 | { | ||
107 | if (!this.OverwriteHash) | ||
108 | { | ||
109 | // not overwriting hash, so don't do the rest of these options. | ||
110 | } | ||
111 | else if (null != file.File.Version) | ||
112 | { | ||
113 | // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks | ||
114 | // 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. | ||
115 | // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing | ||
116 | // all the file rows) for a relatively uncommon situation. Let's not do that. | ||
117 | // | ||
118 | // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version | ||
119 | // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. | ||
120 | if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) | ||
121 | { | ||
122 | Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File)); | ||
123 | } | ||
124 | } | ||
125 | else | ||
126 | { | ||
127 | if (null != file.File.Language) | ||
128 | { | ||
129 | Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); | ||
130 | } | ||
131 | |||
132 | int[] hash; | ||
133 | try | ||
134 | { | ||
135 | Installer.GetFileHash(fileInfo.FullName, 0, out hash); | ||
136 | } | ||
137 | catch (Win32Exception e) | ||
138 | { | ||
139 | if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND | ||
140 | { | ||
141 | throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName)); | ||
142 | } | ||
143 | else | ||
144 | { | ||
145 | throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); | ||
146 | } | ||
147 | } | ||
148 | |||
149 | if (null == file.Hash) | ||
150 | { | ||
151 | Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]); | ||
152 | file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers); | ||
153 | } | ||
154 | |||
155 | file.Hash[0] = file.File.File; | ||
156 | file.Hash[1] = 0; | ||
157 | file.Hash[2] = hash[0]; | ||
158 | file.Hash[3] = hash[1]; | ||
159 | file.Hash[4] = hash[2]; | ||
160 | file.Hash[5] = hash[3]; | ||
161 | } | ||
162 | } | ||
163 | else // update the file row with the version and language information. | ||
164 | { | ||
165 | // If no version was provided by the user, use the version from the file itself. | ||
166 | // This is the most common case. | ||
167 | if (String.IsNullOrEmpty(file.File.Version)) | ||
168 | { | ||
169 | file.File.Version = version; | ||
170 | } | ||
171 | else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) // this looks expensive, but see explanation below. | ||
172 | { | ||
173 | // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching | ||
174 | // the version value). We didn't find it so, we will override the default version they provided with the actual | ||
175 | // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match | ||
176 | // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case | ||
177 | // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more | ||
178 | // CPU intensive search to save on the memory intensive index that wouldn't be used much. | ||
179 | // | ||
180 | // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. | ||
181 | // That's typically even more rare than companion files so again, no index, just search. | ||
182 | file.File.Version = version; | ||
183 | } | ||
184 | |||
185 | if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language)) | ||
186 | { | ||
187 | Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File)); | ||
188 | } | ||
189 | else // override the default provided by the user (usually nothing) with the actual language from the file itself. | ||
190 | { | ||
191 | file.File.Language = language; | ||
192 | } | ||
193 | |||
194 | // Populate the binder variables for this file information if requested. | ||
195 | if (null != this.VariableCache) | ||
196 | { | ||
197 | if (!String.IsNullOrEmpty(file.File.Version)) | ||
198 | { | ||
199 | string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)); | ||
200 | this.VariableCache[key] = file.File.Version; | ||
201 | } | ||
202 | |||
203 | if (!String.IsNullOrEmpty(file.File.Language)) | ||
204 | { | ||
205 | string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", Common.Demodularize(this.Output.Type, ModularizationGuid, file.File.File)); | ||
206 | this.VariableCache[key] = file.File.Language; | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | |||
211 | // If this is a CLR assembly, load the assembly and get the assembly name information | ||
212 | if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType) | ||
213 | { | ||
214 | bool targetNetfx1 = false; | ||
215 | StringDictionary assemblyNameValues = new StringDictionary(); | ||
216 | |||
217 | ClrInterop.IReferenceIdentity referenceIdentity = null; | ||
218 | Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid; | ||
219 | uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity); | ||
220 | if (0 == result && null != referenceIdentity) | ||
221 | { | ||
222 | string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion"); | ||
223 | if (null != imageRuntimeVersion) | ||
224 | { | ||
225 | targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase); | ||
226 | } | ||
227 | |||
228 | string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral"; | ||
229 | assemblyNameValues.Add("Culture", culture); | ||
230 | |||
231 | string name = referenceIdentity.GetAttribute(null, "Name"); | ||
232 | if (null != name) | ||
233 | { | ||
234 | assemblyNameValues.Add("Name", name); | ||
235 | } | ||
236 | |||
237 | string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture"); | ||
238 | if (null != processorArchitecture) | ||
239 | { | ||
240 | assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture); | ||
241 | } | ||
242 | |||
243 | string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken"); | ||
244 | if (null != publicKeyToken) | ||
245 | { | ||
246 | bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase)); | ||
247 | |||
248 | // Managed code expects "null" instead of "neutral", and | ||
249 | // this won't be installed to the GAC since it's not signed anyway. | ||
250 | assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant()); | ||
251 | assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken); | ||
252 | } | ||
253 | else if (file.WixFile.AssemblyApplication == null) | ||
254 | { | ||
255 | throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component)); | ||
256 | } | ||
257 | |||
258 | string assemblyVersion = referenceIdentity.GetAttribute(null, "Version"); | ||
259 | if (null != version) | ||
260 | { | ||
261 | assemblyNameValues.Add("Version", assemblyVersion); | ||
262 | } | ||
263 | } | ||
264 | else | ||
265 | { | ||
266 | Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result))); | ||
267 | return; | ||
268 | } | ||
269 | |||
270 | Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
271 | if (assemblyNameValues.ContainsKey("name")) | ||
272 | { | ||
273 | this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]); | ||
274 | } | ||
275 | |||
276 | if (!String.IsNullOrEmpty(version)) | ||
277 | { | ||
278 | this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version); | ||
279 | } | ||
280 | |||
281 | if (assemblyNameValues.ContainsKey("version")) | ||
282 | { | ||
283 | string assemblyVersion = assemblyNameValues["version"]; | ||
284 | |||
285 | if (!targetNetfx1) | ||
286 | { | ||
287 | // There is a bug in v1 fusion that requires the assembly's "version" attribute | ||
288 | // to be equal to or longer than the "fileVersion" in length when its present; | ||
289 | // the workaround is to prepend zeroes to the last version number in the assembly | ||
290 | // version. | ||
291 | if (null != version && version.Length > assemblyVersion.Length) | ||
292 | { | ||
293 | string padding = new string('0', version.Length - assemblyVersion.Length); | ||
294 | string[] assemblyVersionNumbers = assemblyVersion.Split('.'); | ||
295 | |||
296 | if (assemblyVersionNumbers.Length > 0) | ||
297 | { | ||
298 | assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]); | ||
299 | assemblyVersion = String.Join(".", assemblyVersionNumbers); | ||
300 | } | ||
301 | } | ||
302 | } | ||
303 | |||
304 | this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion); | ||
305 | } | ||
306 | |||
307 | if (assemblyNameValues.ContainsKey("culture")) | ||
308 | { | ||
309 | this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]); | ||
310 | } | ||
311 | |||
312 | if (assemblyNameValues.ContainsKey("publicKeyToken")) | ||
313 | { | ||
314 | this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]); | ||
315 | } | ||
316 | |||
317 | if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) | ||
318 | { | ||
319 | this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); | ||
320 | } | ||
321 | |||
322 | if (assemblyNameValues.ContainsKey("processorArchitecture")) | ||
323 | { | ||
324 | this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]); | ||
325 | } | ||
326 | |||
327 | // add the assembly name to the information cache | ||
328 | if (null != this.VariableCache) | ||
329 | { | ||
330 | string fileId = Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File); | ||
331 | string key = String.Concat("assemblyfullname.", fileId); | ||
332 | string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]); | ||
333 | if (assemblyNameValues.ContainsKey("processorArchitecture")) | ||
334 | { | ||
335 | assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]); | ||
336 | } | ||
337 | |||
338 | this.VariableCache[key] = assemblyName; | ||
339 | |||
340 | // Add entries with the preserved case publicKeyToken | ||
341 | string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId); | ||
342 | this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]); | ||
343 | |||
344 | string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId); | ||
345 | this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"]; | ||
346 | } | ||
347 | } | ||
348 | else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType) | ||
349 | { | ||
350 | // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through | ||
351 | // all files like this. Even though this is a rare case it looks like we might be able to index the | ||
352 | // file earlier. | ||
353 | FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal)); | ||
354 | if (null == fileManifest) | ||
355 | { | ||
356 | Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest)); | ||
357 | } | ||
358 | |||
359 | string win32Type = null; | ||
360 | string win32Name = null; | ||
361 | string win32Version = null; | ||
362 | string win32ProcessorArchitecture = null; | ||
363 | string win32PublicKeyToken = null; | ||
364 | |||
365 | // loading the dom is expensive we want more performant APIs than the DOM | ||
366 | // Navigator is cheaper than dom. Perhaps there is a cheaper API still. | ||
367 | try | ||
368 | { | ||
369 | XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source); | ||
370 | XPathNavigator nav = doc.CreateNavigator(); | ||
371 | nav.MoveToRoot(); | ||
372 | |||
373 | // this assumes a particular schema for a win32 manifest and does not | ||
374 | // provide error checking if the file does not conform to schema. | ||
375 | // The fallback case here is that nothing is added to the MsiAssemblyName | ||
376 | // table for an out of tolerance Win32 manifest. Perhaps warnings needed. | ||
377 | if (nav.MoveToFirstChild()) | ||
378 | { | ||
379 | while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") | ||
380 | { | ||
381 | nav.MoveToNext(); | ||
382 | } | ||
383 | |||
384 | if (nav.MoveToFirstChild()) | ||
385 | { | ||
386 | bool hasNextSibling = true; | ||
387 | while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) | ||
388 | { | ||
389 | hasNextSibling = nav.MoveToNext(); | ||
390 | } | ||
391 | if (!hasNextSibling) | ||
392 | { | ||
393 | Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source)); | ||
394 | return; | ||
395 | } | ||
396 | |||
397 | if (nav.MoveToAttribute("type", String.Empty)) | ||
398 | { | ||
399 | win32Type = nav.Value; | ||
400 | nav.MoveToParent(); | ||
401 | } | ||
402 | |||
403 | if (nav.MoveToAttribute("name", String.Empty)) | ||
404 | { | ||
405 | win32Name = nav.Value; | ||
406 | nav.MoveToParent(); | ||
407 | } | ||
408 | |||
409 | if (nav.MoveToAttribute("version", String.Empty)) | ||
410 | { | ||
411 | win32Version = nav.Value; | ||
412 | nav.MoveToParent(); | ||
413 | } | ||
414 | |||
415 | if (nav.MoveToAttribute("processorArchitecture", String.Empty)) | ||
416 | { | ||
417 | win32ProcessorArchitecture = nav.Value; | ||
418 | nav.MoveToParent(); | ||
419 | } | ||
420 | |||
421 | if (nav.MoveToAttribute("publicKeyToken", String.Empty)) | ||
422 | { | ||
423 | win32PublicKeyToken = nav.Value; | ||
424 | nav.MoveToParent(); | ||
425 | } | ||
426 | } | ||
427 | } | ||
428 | } | ||
429 | catch (FileNotFoundException fe) | ||
430 | { | ||
431 | Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest")); | ||
432 | } | ||
433 | catch (XmlException xe) | ||
434 | { | ||
435 | Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message)); | ||
436 | } | ||
437 | |||
438 | Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
439 | if (!String.IsNullOrEmpty(win32Name)) | ||
440 | { | ||
441 | this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name); | ||
442 | } | ||
443 | |||
444 | if (!String.IsNullOrEmpty(win32Version)) | ||
445 | { | ||
446 | this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version); | ||
447 | } | ||
448 | |||
449 | if (!String.IsNullOrEmpty(win32Type)) | ||
450 | { | ||
451 | this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type); | ||
452 | } | ||
453 | |||
454 | if (!String.IsNullOrEmpty(win32ProcessorArchitecture)) | ||
455 | { | ||
456 | this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture); | ||
457 | } | ||
458 | |||
459 | if (!String.IsNullOrEmpty(win32PublicKeyToken)) | ||
460 | { | ||
461 | this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken); | ||
462 | } | ||
463 | } | ||
464 | } | ||
465 | |||
466 | /// <summary> | ||
467 | /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise | ||
468 | /// create a new row. | ||
469 | /// </summary> | ||
470 | /// <param name="assemblyNameTable">MsiAssemblyName table.</param> | ||
471 | /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param> | ||
472 | /// <param name="name">MsiAssemblyName name.</param> | ||
473 | /// <param name="value">MsiAssemblyName value.</param> | ||
474 | private void SetMsiAssemblyName(Table assemblyNameTable, FileFacade file, string name, string value) | ||
475 | { | ||
476 | // check for null value (this can occur when grabbing the file version from an assembly without one) | ||
477 | if (String.IsNullOrEmpty(value)) | ||
478 | { | ||
479 | Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name)); | ||
480 | } | ||
481 | else | ||
482 | { | ||
483 | Row assemblyNameRow = null; | ||
484 | |||
485 | // override directly authored value | ||
486 | foreach (Row row in assemblyNameTable.Rows) | ||
487 | { | ||
488 | if ((string)row[0] == file.File.Component && (string)row[1] == name) | ||
489 | { | ||
490 | assemblyNameRow = row; | ||
491 | break; | ||
492 | } | ||
493 | } | ||
494 | |||
495 | // 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. | ||
496 | if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType && | ||
497 | String.IsNullOrEmpty(file.WixFile.AssemblyApplication) && | ||
498 | !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase)) | ||
499 | { | ||
500 | Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value)); | ||
501 | } | ||
502 | |||
503 | if (null == assemblyNameRow) | ||
504 | { | ||
505 | assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers); | ||
506 | assemblyNameRow[0] = file.File.Component; | ||
507 | assemblyNameRow[1] = name; | ||
508 | assemblyNameRow[2] = value; | ||
509 | |||
510 | // put the MsiAssemblyName row in the same section as the related File row | ||
511 | assemblyNameRow.SectionId = file.File.SectionId; | ||
512 | |||
513 | if (null == file.AssemblyNames) | ||
514 | { | ||
515 | file.AssemblyNames = new List<Row>(); | ||
516 | } | ||
517 | |||
518 | file.AssemblyNames.Add(assemblyNameRow); | ||
519 | } | ||
520 | else | ||
521 | { | ||
522 | assemblyNameRow[2] = value; | ||
523 | } | ||
524 | |||
525 | if (this.VariableCache != null) | ||
526 | { | ||
527 | string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant(); | ||
528 | this.VariableCache[key] = (string)assemblyNameRow[2]; | ||
529 | } | ||
530 | } | ||
531 | } | ||
532 | } | ||
533 | } | ||