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