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/Patch.cs | |
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/Patch.cs')
-rw-r--r-- | src/WixToolset.Core/Patch.cs | 1284 |
1 files changed, 0 insertions, 1284 deletions
diff --git a/src/WixToolset.Core/Patch.cs b/src/WixToolset.Core/Patch.cs deleted file mode 100644 index e3e6c27f..00000000 --- a/src/WixToolset.Core/Patch.cs +++ /dev/null | |||
@@ -1,1284 +0,0 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Data | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | using System.Globalization; | ||
10 | using WixToolset.Data.Rows; | ||
11 | using WixToolset.Extensibility; | ||
12 | using WixToolset.Msi; | ||
13 | using WixToolset.Core.Native; | ||
14 | |||
15 | /// <summary> | ||
16 | /// Values for the OptimizeCA MsiPatchMetdata property, which indicates whether custom actions can be skipped when applying the patch. | ||
17 | /// </summary> | ||
18 | [Flags] | ||
19 | internal enum OptimizeCA | ||
20 | { | ||
21 | /// <summary> | ||
22 | /// No custom actions are skipped. | ||
23 | /// </summary> | ||
24 | None = 0, | ||
25 | |||
26 | /// <summary> | ||
27 | /// Skip property (type 51) and directory (type 35) assignment custom actions. | ||
28 | /// </summary> | ||
29 | SkipAssignment = 1, | ||
30 | |||
31 | /// <summary> | ||
32 | /// Skip immediate custom actions that are not property or directory assignment custom actions. | ||
33 | /// </summary> | ||
34 | SkipImmediate = 2, | ||
35 | |||
36 | /// <summary> | ||
37 | /// Skip custom actions that run within the script. | ||
38 | /// </summary> | ||
39 | SkipDeferred = 4, | ||
40 | } | ||
41 | |||
42 | /// <summary> | ||
43 | /// Contains output tables and logic for building an MSP package. | ||
44 | /// </summary> | ||
45 | public class Patch | ||
46 | { | ||
47 | private List<IInspectorExtension> inspectorExtensions; | ||
48 | private Output patch; | ||
49 | private TableDefinitionCollection tableDefinitions; | ||
50 | |||
51 | public Output PatchOutput | ||
52 | { | ||
53 | get { return this.patch; } | ||
54 | } | ||
55 | |||
56 | public Patch() | ||
57 | { | ||
58 | this.inspectorExtensions = new List<IInspectorExtension>(); | ||
59 | this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions()); | ||
60 | } | ||
61 | |||
62 | /// <summary> | ||
63 | /// Adds an extension. | ||
64 | /// </summary> | ||
65 | /// <param name="extension">The extension to add.</param> | ||
66 | public void AddExtension(IInspectorExtension extension) | ||
67 | { | ||
68 | this.inspectorExtensions.Add(extension); | ||
69 | } | ||
70 | |||
71 | public void Load(string patchPath) | ||
72 | { | ||
73 | this.patch = Output.Load(patchPath, false); | ||
74 | } | ||
75 | |||
76 | /// <summary> | ||
77 | /// Include transforms in a patch. | ||
78 | /// </summary> | ||
79 | /// <param name="transforms">List of transforms to attach.</param> | ||
80 | [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] | ||
81 | public void AttachTransforms(List<PatchTransform> transforms) | ||
82 | { | ||
83 | InspectorCore inspectorCore = new InspectorCore(); | ||
84 | |||
85 | // Track if at least one transform gets attached. | ||
86 | bool attachedTransform = false; | ||
87 | |||
88 | if (transforms == null || transforms.Count == 0) | ||
89 | { | ||
90 | throw new WixException(WixErrors.PatchWithoutTransforms()); | ||
91 | } | ||
92 | |||
93 | // Get the patch id from the WixPatchId table. | ||
94 | string patchId = null; | ||
95 | string clientPatchId = null; | ||
96 | Table wixPatchIdTable = this.patch.Tables["WixPatchId"]; | ||
97 | if (null != wixPatchIdTable && 0 < wixPatchIdTable.Rows.Count) | ||
98 | { | ||
99 | Row patchIdRow = wixPatchIdTable.Rows[0]; | ||
100 | if (null != patchIdRow) | ||
101 | { | ||
102 | patchId = patchIdRow[0].ToString(); | ||
103 | clientPatchId = patchIdRow[1].ToString(); | ||
104 | } | ||
105 | } | ||
106 | |||
107 | if (null == patchId) | ||
108 | { | ||
109 | throw new WixException(WixErrors.ExpectedPatchIdInWixMsp()); | ||
110 | } | ||
111 | if (null == clientPatchId) | ||
112 | { | ||
113 | throw new WixException(WixErrors.ExpectedClientPatchIdInWixMsp()); | ||
114 | } | ||
115 | |||
116 | // enumerate patch.Media to map diskId to Media row | ||
117 | Table patchMediaTable = patch.Tables["Media"]; | ||
118 | |||
119 | if (null == patchMediaTable || patchMediaTable.Rows.Count == 0) | ||
120 | { | ||
121 | throw new WixException(WixErrors.ExpectedMediaRowsInWixMsp()); | ||
122 | } | ||
123 | |||
124 | Hashtable mediaRows = new Hashtable(patchMediaTable.Rows.Count); | ||
125 | foreach (MediaRow row in patchMediaTable.Rows) | ||
126 | { | ||
127 | int media = row.DiskId; | ||
128 | mediaRows[media] = row; | ||
129 | } | ||
130 | |||
131 | // enumerate patch.WixPatchBaseline to map baseline to diskId | ||
132 | Table patchBaselineTable = patch.Tables["WixPatchBaseline"]; | ||
133 | |||
134 | int numPatchBaselineRows = (null != patchBaselineTable) ? patchBaselineTable.Rows.Count : 0; | ||
135 | |||
136 | Hashtable baselineMedia = new Hashtable(numPatchBaselineRows); | ||
137 | if (patchBaselineTable != null) | ||
138 | { | ||
139 | foreach (Row row in patchBaselineTable.Rows) | ||
140 | { | ||
141 | string baseline = (string)row[0]; | ||
142 | int media = (int)row[1]; | ||
143 | int validationFlags = (int)row[2]; | ||
144 | if (baselineMedia.Contains(baseline)) | ||
145 | { | ||
146 | this.OnMessage(WixErrors.SamePatchBaselineId(row.SourceLineNumbers, baseline)); | ||
147 | } | ||
148 | baselineMedia[baseline] = new int[] { media, validationFlags }; | ||
149 | } | ||
150 | } | ||
151 | |||
152 | // populate MSP summary information | ||
153 | Table patchSummaryInfo = patch.EnsureTable(this.tableDefinitions["_SummaryInformation"]); | ||
154 | |||
155 | // Remove properties that will be calculated or are reserved. | ||
156 | for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--) | ||
157 | { | ||
158 | Row row = patchSummaryInfo.Rows[i]; | ||
159 | switch ((SummaryInformation.Patch)row[0]) | ||
160 | { | ||
161 | case SummaryInformation.Patch.ProductCodes: | ||
162 | case SummaryInformation.Patch.TransformNames: | ||
163 | case SummaryInformation.Patch.PatchCode: | ||
164 | case SummaryInformation.Patch.InstallerRequirement: | ||
165 | case SummaryInformation.Patch.Reserved11: | ||
166 | case SummaryInformation.Patch.Reserved14: | ||
167 | case SummaryInformation.Patch.Reserved16: | ||
168 | patchSummaryInfo.Rows.RemoveAt(i); | ||
169 | break; | ||
170 | } | ||
171 | } | ||
172 | |||
173 | // Index remaining summary properties. | ||
174 | SummaryInfoRowCollection summaryInfo = new SummaryInfoRowCollection(patchSummaryInfo); | ||
175 | |||
176 | // PID_CODEPAGE | ||
177 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage)) | ||
178 | { | ||
179 | // set the code page by default to the same code page for the | ||
180 | // string pool in the database. | ||
181 | Row codePage = patchSummaryInfo.CreateRow(null); | ||
182 | codePage[0] = (int)SummaryInformation.Patch.CodePage; | ||
183 | codePage[1] = this.patch.Codepage.ToString(CultureInfo.InvariantCulture); | ||
184 | } | ||
185 | |||
186 | // GUID patch code for the patch. | ||
187 | Row revisionRow = patchSummaryInfo.CreateRow(null); | ||
188 | revisionRow[0] = (int)SummaryInformation.Patch.PatchCode; | ||
189 | revisionRow[1] = patchId; | ||
190 | |||
191 | // Indicates the minimum Windows Installer version that is required to install the patch. | ||
192 | Row wordsRow = patchSummaryInfo.CreateRow(null); | ||
193 | wordsRow[0] = (int)SummaryInformation.Patch.InstallerRequirement; | ||
194 | wordsRow[1] = ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture); | ||
195 | |||
196 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.Security)) | ||
197 | { | ||
198 | Row security = patchSummaryInfo.CreateRow(null); | ||
199 | security[0] = (int)SummaryInformation.Patch.Security; | ||
200 | security[1] = "4"; // Read-only enforced | ||
201 | } | ||
202 | |||
203 | // use authored comments or default to DisplayName (required) | ||
204 | string comments = null; | ||
205 | |||
206 | Table msiPatchMetadataTable = patch.Tables["MsiPatchMetadata"]; | ||
207 | Hashtable metadataTable = new Hashtable(); | ||
208 | if (null != msiPatchMetadataTable) | ||
209 | { | ||
210 | foreach (Row row in msiPatchMetadataTable.Rows) | ||
211 | { | ||
212 | metadataTable.Add(row.Fields[1].Data.ToString(), row.Fields[2].Data.ToString()); | ||
213 | } | ||
214 | |||
215 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.Title) && metadataTable.Contains("DisplayName")) | ||
216 | { | ||
217 | string displayName = (string)metadataTable["DisplayName"]; | ||
218 | |||
219 | Row title = patchSummaryInfo.CreateRow(null); | ||
220 | title[0] = (int)SummaryInformation.Patch.Title; | ||
221 | title[1] = displayName; | ||
222 | |||
223 | // default comments use DisplayName as-is (no loc) | ||
224 | comments = displayName; | ||
225 | } | ||
226 | |||
227 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage) && metadataTable.Contains("CodePage")) | ||
228 | { | ||
229 | Row codePage = patchSummaryInfo.CreateRow(null); | ||
230 | codePage[0] = (int)SummaryInformation.Patch.CodePage; | ||
231 | codePage[1] = metadataTable["CodePage"]; | ||
232 | } | ||
233 | |||
234 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.PackageName) && metadataTable.Contains("Description")) | ||
235 | { | ||
236 | Row subject = patchSummaryInfo.CreateRow(null); | ||
237 | subject[0] = (int)SummaryInformation.Patch.PackageName; | ||
238 | subject[1] = metadataTable["Description"]; | ||
239 | } | ||
240 | |||
241 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.Manufacturer) && metadataTable.Contains("ManufacturerName")) | ||
242 | { | ||
243 | Row author = patchSummaryInfo.CreateRow(null); | ||
244 | author[0] = (int)SummaryInformation.Patch.Manufacturer; | ||
245 | author[1] = metadataTable["ManufacturerName"]; | ||
246 | } | ||
247 | } | ||
248 | |||
249 | // special metadata marshalled through the build | ||
250 | Table wixPatchMetadataTable = patch.Tables["WixPatchMetadata"]; | ||
251 | Hashtable wixMetadataTable = new Hashtable(); | ||
252 | if (null != wixPatchMetadataTable) | ||
253 | { | ||
254 | foreach (Row row in wixPatchMetadataTable.Rows) | ||
255 | { | ||
256 | wixMetadataTable.Add(row.Fields[0].Data.ToString(), row.Fields[1].Data.ToString()); | ||
257 | } | ||
258 | |||
259 | if (wixMetadataTable.Contains("Comments")) | ||
260 | { | ||
261 | comments = (string)wixMetadataTable["Comments"]; | ||
262 | } | ||
263 | } | ||
264 | |||
265 | // write the package comments to summary info | ||
266 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.Comments) && null != comments) | ||
267 | { | ||
268 | Row commentsRow = patchSummaryInfo.CreateRow(null); | ||
269 | commentsRow[0] = (int)SummaryInformation.Patch.Comments; | ||
270 | commentsRow[1] = comments; | ||
271 | } | ||
272 | |||
273 | // enumerate transforms | ||
274 | Dictionary<string, object> productCodes = new Dictionary<string, object>(); | ||
275 | ArrayList transformNames = new ArrayList(); | ||
276 | ArrayList validTransform = new ArrayList(); | ||
277 | int transformCount = 0; | ||
278 | foreach (PatchTransform mainTransform in transforms) | ||
279 | { | ||
280 | string baseline = null; | ||
281 | int media = -1; | ||
282 | int validationFlags = 0; | ||
283 | |||
284 | if (baselineMedia.Contains(mainTransform.Baseline)) | ||
285 | { | ||
286 | int[] baselineData = (int[])baselineMedia[mainTransform.Baseline]; | ||
287 | int newMedia = baselineData[0]; | ||
288 | if (media != -1 && media != newMedia) | ||
289 | { | ||
290 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_TransformAuthoredIntoMultipleMedia, media, newMedia)); | ||
291 | } | ||
292 | baseline = mainTransform.Baseline; | ||
293 | media = newMedia; | ||
294 | validationFlags = baselineData[1]; | ||
295 | } | ||
296 | |||
297 | if (media == -1) | ||
298 | { | ||
299 | // transform's baseline not attached to any Media | ||
300 | continue; | ||
301 | } | ||
302 | |||
303 | Table patchRefTable = patch.Tables["WixPatchRef"]; | ||
304 | if (patchRefTable != null && patchRefTable.Rows.Count > 0) | ||
305 | { | ||
306 | if (!Patch.ReduceTransform(mainTransform.Transform, patchRefTable)) | ||
307 | { | ||
308 | // transform has none of the content authored into this patch | ||
309 | continue; | ||
310 | } | ||
311 | } | ||
312 | |||
313 | // Validate the transform doesn't break any patch specific rules. | ||
314 | mainTransform.Validate(); | ||
315 | |||
316 | // ensure consistent File.Sequence within each Media | ||
317 | MediaRow mediaRow = (MediaRow)mediaRows[media]; | ||
318 | |||
319 | // Ensure that files are sequenced after the last file in any transform. | ||
320 | Table transformMediaTable = mainTransform.Transform.Tables["Media"]; | ||
321 | if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) | ||
322 | { | ||
323 | foreach (MediaRow transformMediaRow in transformMediaTable.Rows) | ||
324 | { | ||
325 | if (mediaRow.LastSequence < transformMediaRow.LastSequence) | ||
326 | { | ||
327 | // The Binder will pre-increment the sequence. | ||
328 | mediaRow.LastSequence = transformMediaRow.LastSequence; | ||
329 | } | ||
330 | } | ||
331 | } | ||
332 | |||
333 | // Use the Media/@DiskId if greater for backward compatibility. | ||
334 | if (mediaRow.LastSequence < mediaRow.DiskId) | ||
335 | { | ||
336 | mediaRow.LastSequence = mediaRow.DiskId; | ||
337 | } | ||
338 | |||
339 | // ignore media table from transform. | ||
340 | mainTransform.Transform.Tables.Remove("Media"); | ||
341 | mainTransform.Transform.Tables.Remove("WixMedia"); | ||
342 | mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); | ||
343 | |||
344 | string productCode; | ||
345 | Output pairedTransform = this.BuildPairedTransform(patchId, clientPatchId, mainTransform.Transform, mediaRow, validationFlags, out productCode); | ||
346 | productCodes[productCode] = null; | ||
347 | DictionaryEntry entry = new DictionaryEntry(); | ||
348 | entry.Key = productCode; | ||
349 | entry.Value = mainTransform.Transform; | ||
350 | validTransform.Add(entry); | ||
351 | |||
352 | // attach these transforms to the patch object | ||
353 | // TODO: is this an acceptable way to auto-generate transform stream names? | ||
354 | string transformName = baseline + "." + (++transformCount).ToString(CultureInfo.InvariantCulture); | ||
355 | patch.SubStorages.Add(new SubStorage(transformName, mainTransform.Transform)); | ||
356 | patch.SubStorages.Add(new SubStorage("#" + transformName, pairedTransform)); | ||
357 | transformNames.Add(":" + transformName); | ||
358 | transformNames.Add(":#" + transformName); | ||
359 | attachedTransform = true; | ||
360 | } | ||
361 | |||
362 | if (!attachedTransform) | ||
363 | { | ||
364 | throw new WixException(WixErrors.PatchWithoutValidTransforms()); | ||
365 | } | ||
366 | |||
367 | // Validate that a patch authored as removable is actually removable | ||
368 | if (metadataTable.Contains("AllowRemoval")) | ||
369 | { | ||
370 | if ("1" == metadataTable["AllowRemoval"].ToString()) | ||
371 | { | ||
372 | ArrayList tables = Patch.GetPatchUninstallBreakingTables(); | ||
373 | bool result = true; | ||
374 | foreach (DictionaryEntry entry in validTransform) | ||
375 | { | ||
376 | result &= this.CheckUninstallableTransform(entry.Key.ToString(), (Output)entry.Value, tables); | ||
377 | } | ||
378 | |||
379 | if (!result) | ||
380 | { | ||
381 | throw new WixException(WixErrors.PatchNotRemovable()); | ||
382 | } | ||
383 | } | ||
384 | } | ||
385 | |||
386 | // Finish filling tables with transform-dependent data. | ||
387 | // Semicolon delimited list of the product codes that can accept the patch. | ||
388 | Table wixPatchTargetTable = patch.Tables["WixPatchTarget"]; | ||
389 | if (null != wixPatchTargetTable) | ||
390 | { | ||
391 | Dictionary<string, object> targets = new Dictionary<string, object>(); | ||
392 | bool replace = true; | ||
393 | foreach (Row wixPatchTargetRow in wixPatchTargetTable.Rows) | ||
394 | { | ||
395 | string target = wixPatchTargetRow[0].ToString(); | ||
396 | if (0 == String.CompareOrdinal("*", target)) | ||
397 | { | ||
398 | replace = false; | ||
399 | } | ||
400 | else | ||
401 | { | ||
402 | targets[target] = null; | ||
403 | } | ||
404 | } | ||
405 | |||
406 | // Replace the target ProductCodes with the authored list. | ||
407 | if (replace) | ||
408 | { | ||
409 | productCodes = targets; | ||
410 | } | ||
411 | else | ||
412 | { | ||
413 | // Copy the authored target ProductCodes into the list. | ||
414 | foreach (string target in targets.Keys) | ||
415 | { | ||
416 | productCodes[target] = null; | ||
417 | } | ||
418 | } | ||
419 | } | ||
420 | |||
421 | string[] uniqueProductCodes = new string[productCodes.Keys.Count]; | ||
422 | productCodes.Keys.CopyTo(uniqueProductCodes, 0); | ||
423 | |||
424 | Row templateRow = patchSummaryInfo.CreateRow(null); | ||
425 | templateRow[0] = (int)SummaryInformation.Patch.ProductCodes; | ||
426 | templateRow[1] = String.Join(";", uniqueProductCodes); | ||
427 | |||
428 | // Semicolon delimited list of transform substorage names in the order they are applied. | ||
429 | Row savedbyRow = patchSummaryInfo.CreateRow(null); | ||
430 | savedbyRow[0] = (int)SummaryInformation.Patch.TransformNames; | ||
431 | savedbyRow[1] = String.Join(";", (string[])transformNames.ToArray(typeof(string))); | ||
432 | |||
433 | // inspect the patch and filtered transforms | ||
434 | foreach (InspectorExtension inspectorExtension in this.inspectorExtensions) | ||
435 | { | ||
436 | inspectorExtension.Core = inspectorCore; | ||
437 | inspectorExtension.InspectOutput(this.patch); | ||
438 | |||
439 | // reset | ||
440 | inspectorExtension.Core = null; | ||
441 | } | ||
442 | } | ||
443 | |||
444 | /// <summary> | ||
445 | /// Ensure transform is uninstallable. | ||
446 | /// </summary> | ||
447 | /// <param name="productCode">Product code in transform.</param> | ||
448 | /// <param name="transform">Transform generated by torch.</param> | ||
449 | /// <param name="tables">Tables to be checked</param> | ||
450 | /// <returns>True if the transform is uninstallable</returns> | ||
451 | private bool CheckUninstallableTransform(string productCode, Output transform, ArrayList tables) | ||
452 | { | ||
453 | bool ret = true; | ||
454 | foreach (string table in tables) | ||
455 | { | ||
456 | Table wixTable = transform.Tables[table]; | ||
457 | if (null != wixTable) | ||
458 | { | ||
459 | foreach (Row row in wixTable.Rows) | ||
460 | { | ||
461 | if (row.Operation == RowOperation.Add) | ||
462 | { | ||
463 | ret = false; | ||
464 | string primaryKey = row.GetPrimaryKey('/'); | ||
465 | if (null == primaryKey) | ||
466 | { | ||
467 | primaryKey = string.Empty; | ||
468 | } | ||
469 | this.OnMessage(WixErrors.NewRowAddedInTable(row.SourceLineNumbers, productCode, wixTable.Name, primaryKey)); | ||
470 | } | ||
471 | } | ||
472 | } | ||
473 | } | ||
474 | |||
475 | return ret; | ||
476 | } | ||
477 | |||
478 | /// <summary> | ||
479 | /// Tables affect patch uninstall. | ||
480 | /// </summary> | ||
481 | /// <returns>list of tables to be checked</returns> | ||
482 | private static ArrayList GetPatchUninstallBreakingTables() | ||
483 | { | ||
484 | ArrayList tables = new ArrayList(); | ||
485 | tables.Add("AppId"); | ||
486 | tables.Add("BindImage"); | ||
487 | tables.Add("Class"); | ||
488 | tables.Add("Complus"); | ||
489 | tables.Add("CreateFolder"); | ||
490 | tables.Add("DuplicateFile"); | ||
491 | tables.Add("Environment"); | ||
492 | tables.Add("Extension"); | ||
493 | tables.Add("Font"); | ||
494 | tables.Add("IniFile"); | ||
495 | tables.Add("IsolatedComponent"); | ||
496 | tables.Add("LockPermissions"); | ||
497 | tables.Add("MIME"); | ||
498 | tables.Add("MoveFile"); | ||
499 | tables.Add("MsiLockPermissionsEx"); | ||
500 | tables.Add("MsiServiceConfig"); | ||
501 | tables.Add("MsiServiceConfigFailureActions"); | ||
502 | tables.Add("ODBCAttribute"); | ||
503 | tables.Add("ODBCDataSource"); | ||
504 | tables.Add("ODBCDriver"); | ||
505 | tables.Add("ODBCSourceAttribute"); | ||
506 | tables.Add("ODBCTranslator"); | ||
507 | tables.Add("ProgId"); | ||
508 | tables.Add("PublishComponent"); | ||
509 | tables.Add("RemoveIniFile"); | ||
510 | tables.Add("SelfReg"); | ||
511 | tables.Add("ServiceControl"); | ||
512 | tables.Add("ServiceInstall"); | ||
513 | tables.Add("TypeLib"); | ||
514 | tables.Add("Verb"); | ||
515 | |||
516 | return tables; | ||
517 | } | ||
518 | |||
519 | /// <summary> | ||
520 | /// Reduce the transform according to the patch references. | ||
521 | /// </summary> | ||
522 | /// <param name="transform">transform generated by torch.</param> | ||
523 | /// <param name="patchRefTable">Table contains patch family filter.</param> | ||
524 | /// <returns>true if the transform is not empty</returns> | ||
525 | public static bool ReduceTransform(Output transform, Table patchRefTable) | ||
526 | { | ||
527 | // identify sections to keep | ||
528 | Hashtable oldSections = new Hashtable(patchRefTable.Rows.Count); | ||
529 | Hashtable newSections = new Hashtable(patchRefTable.Rows.Count); | ||
530 | Hashtable tableKeyRows = new Hashtable(); | ||
531 | ArrayList sequenceList = new ArrayList(); | ||
532 | Hashtable componentFeatureAddsIndex = new Hashtable(); | ||
533 | Hashtable customActionTable = new Hashtable(); | ||
534 | Hashtable directoryTableAdds = new Hashtable(); | ||
535 | Hashtable featureTableAdds = new Hashtable(); | ||
536 | Hashtable keptComponents = new Hashtable(); | ||
537 | Hashtable keptDirectories = new Hashtable(); | ||
538 | Hashtable keptFeatures = new Hashtable(); | ||
539 | Hashtable keptLockPermissions = new Hashtable(); | ||
540 | Hashtable keptMsiLockPermissionExs = new Hashtable(); | ||
541 | |||
542 | Dictionary<string, List<string>> componentCreateFolderIndex = new Dictionary<string, List<string>>(); | ||
543 | Dictionary<string, List<Row>> directoryLockPermissionsIndex = new Dictionary<string, List<Row>>(); | ||
544 | Dictionary<string, List<Row>> directoryMsiLockPermissionsExIndex = new Dictionary<string, List<Row>>(); | ||
545 | |||
546 | foreach (Row patchRefRow in patchRefTable.Rows) | ||
547 | { | ||
548 | string tableName = (string)patchRefRow[0]; | ||
549 | string key = (string)patchRefRow[1]; | ||
550 | |||
551 | // Short circuit filtering if all changes should be included. | ||
552 | if ("*" == tableName && "*" == key) | ||
553 | { | ||
554 | Patch.RemoveProductCodeFromTransform(transform); | ||
555 | return true; | ||
556 | } | ||
557 | |||
558 | Table table = transform.Tables[tableName]; | ||
559 | if (table == null) | ||
560 | { | ||
561 | // table not found | ||
562 | continue; | ||
563 | } | ||
564 | |||
565 | // index this table | ||
566 | if (!tableKeyRows.Contains(tableName)) | ||
567 | { | ||
568 | Hashtable newKeyRows = new Hashtable(); | ||
569 | foreach (Row newRow in table.Rows) | ||
570 | { | ||
571 | newKeyRows[newRow.GetPrimaryKey('/')] = newRow; | ||
572 | } | ||
573 | tableKeyRows[tableName] = newKeyRows; | ||
574 | } | ||
575 | Hashtable keyRows = (Hashtable)tableKeyRows[tableName]; | ||
576 | |||
577 | Row row = (Row)keyRows[key]; | ||
578 | if (row == null) | ||
579 | { | ||
580 | // row not found | ||
581 | continue; | ||
582 | } | ||
583 | |||
584 | // Differ.sectionDelimiter | ||
585 | string[] sections = row.SectionId.Split('/'); | ||
586 | oldSections[sections[0]] = row; | ||
587 | newSections[sections[1]] = row; | ||
588 | } | ||
589 | |||
590 | // throw away sections not referenced | ||
591 | int keptRows = 0; | ||
592 | Table directoryTable = null; | ||
593 | Table featureTable = null; | ||
594 | Table lockPermissionsTable = null; | ||
595 | Table msiLockPermissionsTable = null; | ||
596 | |||
597 | foreach (Table table in transform.Tables) | ||
598 | { | ||
599 | if ("_SummaryInformation" == table.Name) | ||
600 | { | ||
601 | continue; | ||
602 | } | ||
603 | |||
604 | if (table.Name == "AdminExecuteSequence" | ||
605 | || table.Name == "AdminUISequence" | ||
606 | || table.Name == "AdvtExecuteSequence" | ||
607 | || table.Name == "InstallUISequence" | ||
608 | || table.Name == "InstallExecuteSequence") | ||
609 | { | ||
610 | sequenceList.Add(table); | ||
611 | continue; | ||
612 | } | ||
613 | |||
614 | for (int i = 0; i < table.Rows.Count; i++) | ||
615 | { | ||
616 | Row row = table.Rows[i]; | ||
617 | |||
618 | if (table.Name == "CreateFolder") | ||
619 | { | ||
620 | string createFolderComponentId = (string)row[1]; | ||
621 | |||
622 | List<string> directoryList; | ||
623 | if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out directoryList)) | ||
624 | { | ||
625 | directoryList = new List<string>(); | ||
626 | componentCreateFolderIndex.Add(createFolderComponentId, directoryList); | ||
627 | } | ||
628 | |||
629 | directoryList.Add((string)row[0]); | ||
630 | } | ||
631 | |||
632 | if (table.Name == "CustomAction") | ||
633 | { | ||
634 | customActionTable.Add(row[0], row); | ||
635 | } | ||
636 | |||
637 | if (table.Name == "Directory") | ||
638 | { | ||
639 | directoryTable = table; | ||
640 | if (RowOperation.Add == row.Operation) | ||
641 | { | ||
642 | directoryTableAdds.Add(row[0], row); | ||
643 | } | ||
644 | } | ||
645 | |||
646 | if (table.Name == "Feature") | ||
647 | { | ||
648 | featureTable = table; | ||
649 | if (RowOperation.Add == row.Operation) | ||
650 | { | ||
651 | featureTableAdds.Add(row[0], row); | ||
652 | } | ||
653 | } | ||
654 | |||
655 | if (table.Name == "FeatureComponents") | ||
656 | { | ||
657 | if (RowOperation.Add == row.Operation) | ||
658 | { | ||
659 | string featureId = (string)row[0]; | ||
660 | string componentId = (string)row[1]; | ||
661 | |||
662 | if (componentFeatureAddsIndex.ContainsKey(componentId)) | ||
663 | { | ||
664 | ArrayList featureList = (ArrayList)componentFeatureAddsIndex[componentId]; | ||
665 | featureList.Add(featureId); | ||
666 | } | ||
667 | else | ||
668 | { | ||
669 | ArrayList featureList = new ArrayList(); | ||
670 | componentFeatureAddsIndex.Add(componentId, featureList); | ||
671 | featureList.Add(featureId); | ||
672 | } | ||
673 | } | ||
674 | } | ||
675 | |||
676 | if (table.Name == "LockPermissions") | ||
677 | { | ||
678 | lockPermissionsTable = table; | ||
679 | if ("CreateFolder" == (string)row[1]) | ||
680 | { | ||
681 | string directoryId = (string)row[0]; | ||
682 | |||
683 | List<Row> rowList; | ||
684 | if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out rowList)) | ||
685 | { | ||
686 | rowList = new List<Row>(); | ||
687 | directoryLockPermissionsIndex.Add(directoryId, rowList); | ||
688 | } | ||
689 | |||
690 | rowList.Add(row); | ||
691 | } | ||
692 | } | ||
693 | |||
694 | if (table.Name == "MsiLockPermissionsEx") | ||
695 | { | ||
696 | msiLockPermissionsTable = table; | ||
697 | if ("CreateFolder" == (string)row[1]) | ||
698 | { | ||
699 | string directoryId = (string)row[0]; | ||
700 | |||
701 | List<Row> rowList; | ||
702 | if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out rowList)) | ||
703 | { | ||
704 | rowList = new List<Row>(); | ||
705 | directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); | ||
706 | } | ||
707 | |||
708 | rowList.Add(row); | ||
709 | } | ||
710 | } | ||
711 | |||
712 | if (null == row.SectionId) | ||
713 | { | ||
714 | table.Rows.RemoveAt(i); | ||
715 | i--; | ||
716 | } | ||
717 | else | ||
718 | { | ||
719 | string[] sections = row.SectionId.Split('/'); | ||
720 | // ignore the row without section id. | ||
721 | if (0 == sections[0].Length && 0 == sections[1].Length) | ||
722 | { | ||
723 | table.Rows.RemoveAt(i); | ||
724 | i--; | ||
725 | } | ||
726 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
727 | { | ||
728 | if ("Component" == table.Name) | ||
729 | { | ||
730 | keptComponents.Add((string)row[0], row); | ||
731 | } | ||
732 | |||
733 | if ("Directory" == table.Name) | ||
734 | { | ||
735 | keptDirectories.Add(row[0], row); | ||
736 | } | ||
737 | |||
738 | if ("Feature" == table.Name) | ||
739 | { | ||
740 | keptFeatures.Add(row[0], row); | ||
741 | } | ||
742 | |||
743 | keptRows++; | ||
744 | } | ||
745 | else | ||
746 | { | ||
747 | table.Rows.RemoveAt(i); | ||
748 | i--; | ||
749 | } | ||
750 | } | ||
751 | } | ||
752 | } | ||
753 | |||
754 | keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); | ||
755 | |||
756 | if (null != directoryTable) | ||
757 | { | ||
758 | foreach (Row componentRow in keptComponents.Values) | ||
759 | { | ||
760 | string componentId = (string)componentRow[0]; | ||
761 | |||
762 | if (RowOperation.Add == componentRow.Operation) | ||
763 | { | ||
764 | // make sure each added component has its required directory and feature heirarchy. | ||
765 | string directoryId = (string)componentRow[2]; | ||
766 | while (null != directoryId && directoryTableAdds.ContainsKey(directoryId)) | ||
767 | { | ||
768 | Row directoryRow = (Row)directoryTableAdds[directoryId]; | ||
769 | |||
770 | if (!keptDirectories.ContainsKey(directoryId)) | ||
771 | { | ||
772 | directoryTable.Rows.Add(directoryRow); | ||
773 | keptDirectories.Add(directoryRow[0], null); | ||
774 | keptRows++; | ||
775 | } | ||
776 | |||
777 | directoryId = (string)directoryRow[1]; | ||
778 | } | ||
779 | |||
780 | if (componentFeatureAddsIndex.ContainsKey(componentId)) | ||
781 | { | ||
782 | foreach (string featureId in (ArrayList)componentFeatureAddsIndex[componentId]) | ||
783 | { | ||
784 | string currentFeatureId = featureId; | ||
785 | while (null != currentFeatureId && featureTableAdds.ContainsKey(currentFeatureId)) | ||
786 | { | ||
787 | Row featureRow = (Row)featureTableAdds[currentFeatureId]; | ||
788 | |||
789 | if (!keptFeatures.ContainsKey(currentFeatureId)) | ||
790 | { | ||
791 | featureTable.Rows.Add(featureRow); | ||
792 | keptFeatures.Add(featureRow[0], null); | ||
793 | keptRows++; | ||
794 | } | ||
795 | |||
796 | currentFeatureId = (string)featureRow[1]; | ||
797 | } | ||
798 | } | ||
799 | } | ||
800 | } | ||
801 | |||
802 | // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. | ||
803 | foreach (string keptComponentId in keptComponents.Keys) | ||
804 | { | ||
805 | List<string> directoryList; | ||
806 | if (componentCreateFolderIndex.TryGetValue(keptComponentId, out directoryList)) | ||
807 | { | ||
808 | foreach (string directoryId in directoryList) | ||
809 | { | ||
810 | List<Row> lockPermissionsRowList; | ||
811 | if (directoryLockPermissionsIndex.TryGetValue(directoryId, out lockPermissionsRowList)) | ||
812 | { | ||
813 | foreach (Row lockPermissionsRow in lockPermissionsRowList) | ||
814 | { | ||
815 | string key = lockPermissionsRow.GetPrimaryKey('/'); | ||
816 | if (!keptLockPermissions.ContainsKey(key)) | ||
817 | { | ||
818 | lockPermissionsTable.Rows.Add(lockPermissionsRow); | ||
819 | keptLockPermissions.Add(key, null); | ||
820 | keptRows++; | ||
821 | } | ||
822 | } | ||
823 | } | ||
824 | |||
825 | List<Row> msiLockPermissionsExRowList; | ||
826 | if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out msiLockPermissionsExRowList)) | ||
827 | { | ||
828 | foreach (Row msiLockPermissionsExRow in msiLockPermissionsExRowList) | ||
829 | { | ||
830 | string key = msiLockPermissionsExRow.GetPrimaryKey('/'); | ||
831 | if (!keptMsiLockPermissionExs.ContainsKey(key)) | ||
832 | { | ||
833 | msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); | ||
834 | keptMsiLockPermissionExs.Add(key, null); | ||
835 | keptRows++; | ||
836 | } | ||
837 | } | ||
838 | } | ||
839 | } | ||
840 | } | ||
841 | } | ||
842 | } | ||
843 | } | ||
844 | |||
845 | keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); | ||
846 | |||
847 | // Delete tables that are empty. | ||
848 | ArrayList tablesToDelete = new ArrayList(); | ||
849 | foreach (Table table in transform.Tables) | ||
850 | { | ||
851 | if (0 == table.Rows.Count) | ||
852 | { | ||
853 | tablesToDelete.Add(table.Name); | ||
854 | } | ||
855 | } | ||
856 | |||
857 | // delete separately to avoid messing up enumeration | ||
858 | foreach (string tableName in tablesToDelete) | ||
859 | { | ||
860 | transform.Tables.Remove(tableName); | ||
861 | } | ||
862 | |||
863 | return keptRows > 0; | ||
864 | } | ||
865 | |||
866 | /// <summary> | ||
867 | /// Remove the ProductCode property from the transform. | ||
868 | /// </summary> | ||
869 | /// <param name="transform">The transform.</param> | ||
870 | /// <remarks> | ||
871 | /// Changing the ProductCode is not supported in a patch. | ||
872 | /// </remarks> | ||
873 | private static void RemoveProductCodeFromTransform(Output transform) | ||
874 | { | ||
875 | Table propertyTable = transform.Tables["Property"]; | ||
876 | if (null != propertyTable) | ||
877 | { | ||
878 | for (int i = 0; i < propertyTable.Rows.Count; ++i) | ||
879 | { | ||
880 | Row propertyRow = propertyTable.Rows[i]; | ||
881 | string property = (string)propertyRow[0]; | ||
882 | |||
883 | if ("ProductCode" == property) | ||
884 | { | ||
885 | propertyTable.Rows.RemoveAt(i); | ||
886 | break; | ||
887 | } | ||
888 | } | ||
889 | } | ||
890 | } | ||
891 | |||
892 | /// <summary> | ||
893 | /// Check if the section is in a PatchFamily. | ||
894 | /// </summary> | ||
895 | /// <param name="oldSection">Section id in target wixout</param> | ||
896 | /// <param name="newSection">Section id in upgrade wixout</param> | ||
897 | /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param> | ||
898 | /// <param name="newSections">Hashtable contains section id should be kept in the upgrade wixout.</param> | ||
899 | /// <returns>true if section in patch family</returns> | ||
900 | private static bool IsInPatchFamily(string oldSection, string newSection, Hashtable oldSections, Hashtable newSections) | ||
901 | { | ||
902 | bool result = false; | ||
903 | |||
904 | if ((String.IsNullOrEmpty(oldSection) && newSections.Contains(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.Contains(oldSection))) | ||
905 | { | ||
906 | result = true; | ||
907 | } | ||
908 | else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.Contains(oldSection) || newSections.Contains(newSection))) | ||
909 | { | ||
910 | result = true; | ||
911 | } | ||
912 | |||
913 | return result; | ||
914 | } | ||
915 | |||
916 | /// <summary> | ||
917 | /// Reduce the transform sequence tables. | ||
918 | /// </summary> | ||
919 | /// <param name="sequenceList">ArrayList of tables to be reduced</param> | ||
920 | /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param> | ||
921 | /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param> | ||
922 | /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param> | ||
923 | /// <returns>Number of rows left</returns> | ||
924 | private static int ReduceTransformSequenceTable(ArrayList sequenceList, Hashtable oldSections, Hashtable newSections, Hashtable customAction) | ||
925 | { | ||
926 | int keptRows = 0; | ||
927 | |||
928 | foreach (Table currentTable in sequenceList) | ||
929 | { | ||
930 | for (int i = 0; i < currentTable.Rows.Count; i++) | ||
931 | { | ||
932 | Row row = currentTable.Rows[i]; | ||
933 | string actionName = row.Fields[0].Data.ToString(); | ||
934 | string[] sections = row.SectionId.Split('/'); | ||
935 | bool isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0); | ||
936 | |||
937 | if (row.Operation == RowOperation.None) | ||
938 | { | ||
939 | // ignore the rows without section id. | ||
940 | if (isSectionIdEmpty) | ||
941 | { | ||
942 | currentTable.Rows.RemoveAt(i); | ||
943 | i--; | ||
944 | } | ||
945 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
946 | { | ||
947 | keptRows++; | ||
948 | } | ||
949 | else | ||
950 | { | ||
951 | currentTable.Rows.RemoveAt(i); | ||
952 | i--; | ||
953 | } | ||
954 | } | ||
955 | else if (row.Operation == RowOperation.Modify) | ||
956 | { | ||
957 | bool sequenceChanged = row.Fields[2].Modified; | ||
958 | bool conditionChanged = row.Fields[1].Modified; | ||
959 | |||
960 | if (sequenceChanged && !conditionChanged) | ||
961 | { | ||
962 | keptRows++; | ||
963 | } | ||
964 | else if (!sequenceChanged && conditionChanged) | ||
965 | { | ||
966 | if (isSectionIdEmpty) | ||
967 | { | ||
968 | currentTable.Rows.RemoveAt(i); | ||
969 | i--; | ||
970 | } | ||
971 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
972 | { | ||
973 | keptRows++; | ||
974 | } | ||
975 | else | ||
976 | { | ||
977 | currentTable.Rows.RemoveAt(i); | ||
978 | i--; | ||
979 | } | ||
980 | } | ||
981 | else if (sequenceChanged && conditionChanged) | ||
982 | { | ||
983 | if (isSectionIdEmpty) | ||
984 | { | ||
985 | row.Fields[1].Modified = false; | ||
986 | keptRows++; | ||
987 | } | ||
988 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
989 | { | ||
990 | keptRows++; | ||
991 | } | ||
992 | else | ||
993 | { | ||
994 | row.Fields[1].Modified = false; | ||
995 | keptRows++; | ||
996 | } | ||
997 | } | ||
998 | } | ||
999 | else if (row.Operation == RowOperation.Delete) | ||
1000 | { | ||
1001 | if (isSectionIdEmpty) | ||
1002 | { | ||
1003 | // it is a stardard action which is added by wix, we should keep this action. | ||
1004 | row.Operation = RowOperation.None; | ||
1005 | keptRows++; | ||
1006 | } | ||
1007 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
1008 | { | ||
1009 | keptRows++; | ||
1010 | } | ||
1011 | else | ||
1012 | { | ||
1013 | if (customAction.ContainsKey(actionName)) | ||
1014 | { | ||
1015 | currentTable.Rows.RemoveAt(i); | ||
1016 | i--; | ||
1017 | } | ||
1018 | else | ||
1019 | { | ||
1020 | // it is a stardard action, we should keep this action. | ||
1021 | row.Operation = RowOperation.None; | ||
1022 | keptRows++; | ||
1023 | } | ||
1024 | } | ||
1025 | } | ||
1026 | else if (row.Operation == RowOperation.Add) | ||
1027 | { | ||
1028 | if (isSectionIdEmpty) | ||
1029 | { | ||
1030 | keptRows++; | ||
1031 | } | ||
1032 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
1033 | { | ||
1034 | keptRows++; | ||
1035 | } | ||
1036 | else | ||
1037 | { | ||
1038 | if (customAction.ContainsKey(actionName)) | ||
1039 | { | ||
1040 | currentTable.Rows.RemoveAt(i); | ||
1041 | i--; | ||
1042 | } | ||
1043 | else | ||
1044 | { | ||
1045 | keptRows++; | ||
1046 | } | ||
1047 | } | ||
1048 | } | ||
1049 | } | ||
1050 | } | ||
1051 | |||
1052 | return keptRows; | ||
1053 | } | ||
1054 | |||
1055 | /// <summary> | ||
1056 | /// Create the #transform for the given main transform. | ||
1057 | /// </summary> | ||
1058 | /// <param name="patchId">Patch GUID from patch authoring.</param> | ||
1059 | /// <param name="clientPatchId">Easily referenced identity for this patch.</param> | ||
1060 | /// <param name="mainTransform">Transform generated by torch.</param> | ||
1061 | /// <param name="mediaRow">Media authored into patch.</param> | ||
1062 | /// <param name="validationFlags">Transform validation flags for the summary information stream.</param> | ||
1063 | /// <param name="productCode">Output string to receive ProductCode.</param> | ||
1064 | [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] | ||
1065 | public Output BuildPairedTransform(string patchId, string clientPatchId, Output mainTransform, MediaRow mediaRow, int validationFlags, out string productCode) | ||
1066 | { | ||
1067 | productCode = null; | ||
1068 | Output pairedTransform = new Output(null); | ||
1069 | pairedTransform.Type = OutputType.Transform; | ||
1070 | pairedTransform.Codepage = mainTransform.Codepage; | ||
1071 | |||
1072 | // lookup productVersion property to correct summaryInformation | ||
1073 | string newProductVersion = null; | ||
1074 | Table mainPropertyTable = mainTransform.Tables["Property"]; | ||
1075 | if (null != mainPropertyTable) | ||
1076 | { | ||
1077 | foreach (Row row in mainPropertyTable.Rows) | ||
1078 | { | ||
1079 | if ("ProductVersion" == (string)row[0]) | ||
1080 | { | ||
1081 | newProductVersion = (string)row[1]; | ||
1082 | } | ||
1083 | } | ||
1084 | } | ||
1085 | |||
1086 | // TODO: build class for manipulating SummaryInformation table | ||
1087 | Table mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; | ||
1088 | // add required properties | ||
1089 | Hashtable mainSummaryRows = new Hashtable(); | ||
1090 | foreach (Row mainSummaryRow in mainSummaryTable.Rows) | ||
1091 | { | ||
1092 | mainSummaryRows[mainSummaryRow[0]] = mainSummaryRow; | ||
1093 | } | ||
1094 | if (!mainSummaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) | ||
1095 | { | ||
1096 | Row mainSummaryRow = mainSummaryTable.CreateRow(null); | ||
1097 | mainSummaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; | ||
1098 | mainSummaryRow[1] = validationFlags.ToString(CultureInfo.InvariantCulture); | ||
1099 | } | ||
1100 | |||
1101 | // copy summary information from core transform | ||
1102 | Table pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); | ||
1103 | foreach (Row mainSummaryRow in mainSummaryTable.Rows) | ||
1104 | { | ||
1105 | string value = (string)mainSummaryRow[1]; | ||
1106 | switch ((SummaryInformation.Transform)mainSummaryRow[0]) | ||
1107 | { | ||
1108 | case SummaryInformation.Transform.ProductCodes: | ||
1109 | string[] propertyData = value.Split(';'); | ||
1110 | string oldProductVersion = propertyData[0].Substring(38); | ||
1111 | string upgradeCode = propertyData[2]; | ||
1112 | productCode = propertyData[0].Substring(0, 38); | ||
1113 | if (newProductVersion == null) | ||
1114 | { | ||
1115 | newProductVersion = oldProductVersion; | ||
1116 | } | ||
1117 | |||
1118 | // force mainTranform to old;new;upgrade and pairedTransform to new;new;upgrade | ||
1119 | mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); | ||
1120 | value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); | ||
1121 | break; | ||
1122 | case SummaryInformation.Transform.ValidationFlags: | ||
1123 | // use validation flags authored into the patch XML | ||
1124 | mainSummaryRow[1] = value = validationFlags.ToString(CultureInfo.InvariantCulture); | ||
1125 | break; | ||
1126 | } | ||
1127 | Row pairedSummaryRow = pairedSummaryTable.CreateRow(null); | ||
1128 | pairedSummaryRow[0] = mainSummaryRow[0]; | ||
1129 | pairedSummaryRow[1] = value; | ||
1130 | } | ||
1131 | |||
1132 | if (productCode == null) | ||
1133 | { | ||
1134 | throw new InvalidOperationException(WixStrings.EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo); | ||
1135 | } | ||
1136 | |||
1137 | // copy File table | ||
1138 | Table mainFileTable = mainTransform.Tables["File"]; | ||
1139 | if (null != mainFileTable && 0 < mainFileTable.Rows.Count) | ||
1140 | { | ||
1141 | // We require file source information. | ||
1142 | Table mainWixFileTable = mainTransform.Tables["WixFile"]; | ||
1143 | if (null == mainWixFileTable) | ||
1144 | { | ||
1145 | throw new WixException(WixErrors.AdminImageRequired(productCode)); | ||
1146 | } | ||
1147 | |||
1148 | RowDictionary<FileRow> mainFileRows = new RowDictionary<FileRow>(mainFileTable); | ||
1149 | |||
1150 | Table pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); | ||
1151 | foreach (WixFileRow mainWixFileRow in mainWixFileTable.Rows) | ||
1152 | { | ||
1153 | FileRow mainFileRow = mainFileRows[mainWixFileRow.File]; | ||
1154 | |||
1155 | // set File.Sequence to non null to satisfy transform bind | ||
1156 | mainFileRow.Sequence = 1; | ||
1157 | |||
1158 | // delete's don't need rows in the paired transform | ||
1159 | if (mainFileRow.Operation == RowOperation.Delete) | ||
1160 | { | ||
1161 | continue; | ||
1162 | } | ||
1163 | |||
1164 | FileRow pairedFileRow = (FileRow)pairedFileTable.CreateRow(null); | ||
1165 | pairedFileRow.Operation = RowOperation.Modify; | ||
1166 | for (int i = 0; i < mainFileRow.Fields.Length; i++) | ||
1167 | { | ||
1168 | pairedFileRow[i] = mainFileRow[i]; | ||
1169 | } | ||
1170 | |||
1171 | // override authored media for patch bind | ||
1172 | mainWixFileRow.DiskId = mediaRow.DiskId; | ||
1173 | |||
1174 | // suppress any change to File.Sequence to avoid bloat | ||
1175 | mainFileRow.Fields[7].Modified = false; | ||
1176 | |||
1177 | // force File row to appear in the transform | ||
1178 | switch (mainFileRow.Operation) | ||
1179 | { | ||
1180 | case RowOperation.Modify: | ||
1181 | case RowOperation.Add: | ||
1182 | // set msidbFileAttributesPatchAdded | ||
1183 | pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; | ||
1184 | pairedFileRow.Fields[6].Modified = true; | ||
1185 | pairedFileRow.Operation = mainFileRow.Operation; | ||
1186 | break; | ||
1187 | default: | ||
1188 | pairedFileRow.Fields[6].Modified = false; | ||
1189 | break; | ||
1190 | } | ||
1191 | } | ||
1192 | } | ||
1193 | |||
1194 | // add Media row to pairedTransform | ||
1195 | Table pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); | ||
1196 | Row pairedMediaRow = pairedMediaTable.CreateRow(null); | ||
1197 | pairedMediaRow.Operation = RowOperation.Add; | ||
1198 | for (int i = 0; i < mediaRow.Fields.Length; i++) | ||
1199 | { | ||
1200 | pairedMediaRow[i] = mediaRow[i]; | ||
1201 | } | ||
1202 | |||
1203 | // add PatchPackage for this Media | ||
1204 | Table pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); | ||
1205 | pairedPackageTable.Operation = TableOperation.Add; | ||
1206 | Row pairedPackageRow = pairedPackageTable.CreateRow(null); | ||
1207 | pairedPackageRow.Operation = RowOperation.Add; | ||
1208 | pairedPackageRow[0] = patchId; | ||
1209 | pairedPackageRow[1] = mediaRow.DiskId; | ||
1210 | |||
1211 | // add property to both identify client patches and whether those patches are removable or not | ||
1212 | int allowRemoval = 0; | ||
1213 | Table msiPatchMetadataTable = this.patch.Tables["MsiPatchMetadata"]; | ||
1214 | if (null != msiPatchMetadataTable) | ||
1215 | { | ||
1216 | foreach (Row msiPatchMetadataRow in msiPatchMetadataTable.Rows) | ||
1217 | { | ||
1218 | // get the value of the standard AllowRemoval property, if present | ||
1219 | string company = (string)msiPatchMetadataRow[0]; | ||
1220 | if ((null == company || 0 == company.Length) && "AllowRemoval" == (string)msiPatchMetadataRow[1]) | ||
1221 | { | ||
1222 | allowRemoval = Int32.Parse((string)msiPatchMetadataRow[2], CultureInfo.InvariantCulture); | ||
1223 | } | ||
1224 | } | ||
1225 | } | ||
1226 | |||
1227 | // add the property to the patch transform's Property table | ||
1228 | Table pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); | ||
1229 | pairedPropertyTable.Operation = TableOperation.Add; | ||
1230 | Row pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1231 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1232 | pairedPropertyRow[0] = string.Concat(clientPatchId, ".AllowRemoval"); | ||
1233 | pairedPropertyRow[1] = allowRemoval.ToString(CultureInfo.InvariantCulture); | ||
1234 | |||
1235 | // add this patch code GUID to the patch transform to identify | ||
1236 | // which patches are installed, including in multi-patch | ||
1237 | // installations. | ||
1238 | pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1239 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1240 | pairedPropertyRow[0] = string.Concat(clientPatchId, ".PatchCode"); | ||
1241 | pairedPropertyRow[1] = patchId; | ||
1242 | |||
1243 | // add PATCHNEWPACKAGECODE to apply to admin layouts | ||
1244 | pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1245 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1246 | pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; | ||
1247 | pairedPropertyRow[1] = patchId; | ||
1248 | |||
1249 | // add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts | ||
1250 | Table _summaryInformationTable = this.patch.Tables["_SummaryInformation"]; | ||
1251 | if (null != _summaryInformationTable) | ||
1252 | { | ||
1253 | foreach (Row row in _summaryInformationTable.Rows) | ||
1254 | { | ||
1255 | if (3 == (int)row[0]) // PID_SUBJECT | ||
1256 | { | ||
1257 | pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1258 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1259 | pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; | ||
1260 | pairedPropertyRow[1] = row[1]; | ||
1261 | } | ||
1262 | else if (6 == (int)row[0]) // PID_COMMENTS | ||
1263 | { | ||
1264 | pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1265 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1266 | pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; | ||
1267 | pairedPropertyRow[1] = row[1]; | ||
1268 | } | ||
1269 | } | ||
1270 | } | ||
1271 | |||
1272 | return pairedTransform; | ||
1273 | } | ||
1274 | |||
1275 | /// <summary> | ||
1276 | /// Sends a message to the message delegate if there is one. | ||
1277 | /// </summary> | ||
1278 | /// <param name="mea">Message event arguments.</param> | ||
1279 | public void OnMessage(MessageEventArgs mea) | ||
1280 | { | ||
1281 | Messaging.Instance.OnMessage(mea); | ||
1282 | } | ||
1283 | } | ||
1284 | } | ||