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