diff options
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Patch.cs')
-rw-r--r-- | src/WixToolset.Core.WindowsInstaller/Patch.cs | 1245 |
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 | |||
3 | namespace WixToolset.Data | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | using System.Globalization; | ||
10 | using WixToolset.Data.Rows; | ||
11 | using WixToolset.Extensibility; | ||
12 | using WixToolset.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 | } | ||