aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Patch.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/Patch.cs')
-rw-r--r--src/WixToolset.Core/Patch.cs1284
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
3namespace 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}