summaryrefslogtreecommitdiff
path: root/src/tools/heat/UtilMutator.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/heat/UtilMutator.cs')
-rw-r--r--src/tools/heat/UtilMutator.cs633
1 files changed, 633 insertions, 0 deletions
diff --git a/src/tools/heat/UtilMutator.cs b/src/tools/heat/UtilMutator.cs
new file mode 100644
index 00000000..0bc15cd6
--- /dev/null
+++ b/src/tools/heat/UtilMutator.cs
@@ -0,0 +1,633 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Harvesters
4{
5 using System;
6 using System.Collections;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using WixToolset.Harvesters.Extensibility;
11 using Wix = WixToolset.Harvesters.Serialize;
12
13 /// <summary>
14 /// The template type.
15 /// </summary>
16 public enum TemplateType
17 {
18 /// <summary>
19 /// A fragment template.
20 /// </summary>
21 Fragment,
22
23 /// <summary>
24 /// A module template.
25 /// </summary>
26 Module,
27
28 /// <summary>
29 /// A product template.
30 /// </summary>
31 Package
32 }
33
34 /// <summary>
35 /// The mutator for the WiX Toolset Internet Information Services Extension.
36 /// </summary>
37 public sealed class UtilMutator : BaseMutatorExtension
38 {
39 private ArrayList components;
40 private ArrayList componentGroups;
41 private string componentGroupName;
42 private bool createFragments;
43 private ArrayList directories;
44 private ArrayList directoryRefs;
45 private ArrayList files;
46 private ArrayList features;
47 private SortedList fragments;
48 private bool autogenerateGuids;
49 private bool generateGuids;
50 private string guidFormat = "B"; // Defaults to guid in {}
51 private Wix.IParentElement rootElement;
52 private bool setUniqueIdentifiers;
53 private TemplateType templateType;
54
55 /// <summary>
56 /// Instantiate a new UtilMutator.
57 /// </summary>
58 public UtilMutator()
59 {
60 this.components = new ArrayList();
61 this.componentGroups = new ArrayList();
62 this.directories = new ArrayList();
63 this.directoryRefs = new ArrayList();
64 this.features = new ArrayList();
65 this.files = new ArrayList();
66 this.fragments = new SortedList();
67 }
68
69 /// <summary>
70 /// Gets or sets the value of the component group name.
71 /// </summary>
72 /// <value>The component group name.</value>
73 public string ComponentGroupName
74 {
75 get { return this.componentGroupName; }
76 set { this.componentGroupName = value; }
77 }
78
79 /// <summary>
80 /// Gets or sets the option to create fragments.
81 /// </summary>
82 /// <value>The option to create fragments.</value>
83 public bool CreateFragments
84 {
85 get { return this.createFragments; }
86 set { this.createFragments = value; }
87 }
88
89 /// <summary>
90 /// Gets or sets the option to autogenerate component guids at compile time.
91 /// </summary>
92 /// <value>The option to autogenerate component guids.</value>
93 public bool AutogenerateGuids
94 {
95 get { return this.autogenerateGuids; }
96 set { this.autogenerateGuids = value; }
97 }
98
99 /// <summary>
100 /// Gets or sets the option to generate missing guids.
101 /// </summary>
102 /// <value>The option to generate missing guids.</value>
103 public bool GenerateGuids
104 {
105 get { return this.generateGuids; }
106 set { this.generateGuids = value; }
107 }
108
109 /// <summary>
110 /// Gets or sets the option to set the format of guids.
111 /// D - 32 digits separated by hyphens:
112 /// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
113 /// B - 32 digits separated by hyphens, enclosed in brackets:
114 /// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
115 /// </summary>
116 /// <value>Guid format either B or D.</value>
117 public string GuidFormat
118 {
119 get { return this.guidFormat; }
120 set { this.guidFormat = value; }
121 }
122
123 /// <summary>
124 /// Gets the sequence of the extension.
125 /// </summary>
126 /// <value>The sequence of the extension.</value>
127 public override int Sequence
128 {
129 get { return 1000; }
130 }
131
132 /// <summary>
133 /// Gets of sets the option to set unique identifiers.
134 /// </summary>
135 /// <value>The option to set unique identifiers.</value>
136 public bool SetUniqueIdentifiers
137 {
138 get { return this.setUniqueIdentifiers; }
139 set { this.setUniqueIdentifiers = value; }
140 }
141
142 /// <summary>
143 /// Gets or sets the template type.
144 /// </summary>
145 /// <value>The template type.</value>
146 public TemplateType TemplateType
147 {
148 get { return this.templateType; }
149 set { this.templateType = value; }
150 }
151
152 /// <summary>
153 /// Mutate a WiX document.
154 /// </summary>
155 /// <param name="wix">The Wix document element.</param>
156 public override void Mutate(Wix.Wix wix)
157 {
158 this.components.Clear();
159 this.directories.Clear();
160 this.directoryRefs.Clear();
161 this.features.Clear();
162 this.files.Clear();
163 this.fragments.Clear();
164 this.rootElement = null;
165
166 // index elements in this wix document
167 this.IndexElement(wix);
168
169 this.MutateWix(wix);
170
171 this.MutateFiles();
172
173 this.MutateDirectories();
174
175 this.MutateComponents();
176
177 if (null != this.componentGroupName)
178 {
179 this.CreateComponentGroup(wix);
180 }
181
182 // add the components to the product feature after all the identifiers have been set
183 if (TemplateType.Package == this.templateType)
184 {
185 Wix.Feature feature = (Wix.Feature)this.features[0];
186
187 foreach (Wix.ComponentGroup group in this.componentGroups)
188 {
189 Wix.ComponentGroupRef componentGroupRef = new Wix.ComponentGroupRef();
190 componentGroupRef.Id = group.Id;
191
192 feature.AddChild(componentGroupRef);
193 }
194 }
195 else if (TemplateType.Module == this.templateType)
196 {
197 foreach (Wix.ISchemaElement element in wix.Children)
198 {
199 if (element is Wix.Module)
200 {
201 foreach (Wix.ComponentGroup group in this.componentGroups)
202 {
203 Wix.ComponentGroupRef componentGroupRef = new Wix.ComponentGroupRef();
204 componentGroupRef.Id = group.Id;
205
206 ((Wix.IParentElement)element).AddChild(componentGroupRef);
207 }
208 break;
209 }
210 }
211 }
212
213 //if(!this.createFragments && TemplateType.Package
214 foreach (Wix.Fragment fragment in this.fragments.Values)
215 {
216 wix.AddChild(fragment);
217 }
218 }
219
220 /// <summary>
221 /// Creates a component group with a given name.
222 /// </summary>
223 /// <param name="wix">The Wix document element.</param>
224 private void CreateComponentGroup(Wix.Wix wix)
225 {
226 Wix.ComponentGroup componentGroup = new Wix.ComponentGroup();
227 componentGroup.Id = this.componentGroupName;
228 this.componentGroups.Add(componentGroup);
229
230 Wix.Fragment cgFragment = new Wix.Fragment();
231 cgFragment.AddChild(componentGroup);
232 wix.AddChild(cgFragment);
233
234 int componentCount = 0;
235 for (; componentCount < this.components.Count; componentCount++)
236 {
237 Wix.Component c = this.components[componentCount] as Wix.Component;
238
239 if (this.createFragments)
240 {
241 if (c.ParentElement is Wix.Directory)
242 {
243 Wix.Directory parentDirectory = c.ParentElement as Wix.Directory;
244
245 componentGroup.AddChild(c);
246 c.Directory = parentDirectory.Id;
247 parentDirectory.RemoveChild(c);
248 }
249 else if (c.ParentElement is Wix.DirectoryRef)
250 {
251 Wix.DirectoryRef parentDirectory = c.ParentElement as Wix.DirectoryRef;
252
253 componentGroup.AddChild(c);
254 c.Directory = parentDirectory.Id;
255 parentDirectory.RemoveChild(c);
256
257 // Remove whole fragment if moving the component to the component group just leaves an empty DirectoryRef
258 if (0 < this.fragments.Count && parentDirectory.ParentElement is Wix.Fragment)
259 {
260 Wix.Fragment parentFragment = parentDirectory.ParentElement as Wix.Fragment;
261 int childCount = 0;
262 foreach (Wix.ISchemaElement element in parentFragment.Children)
263 {
264 childCount++;
265 }
266
267 // Component should always have an Id but the SortedList creation allows for null and bases the name on the fragment count which we cannot reverse engineer here.
268 if (1 == childCount && !String.IsNullOrEmpty(c.Id))
269 {
270 int removeIndex = this.fragments.IndexOfKey(String.Concat("Component:", c.Id));
271 if (0 <= removeIndex)
272 {
273 this.fragments.RemoveAt(removeIndex);
274 }
275 }
276 }
277 }
278 }
279 else
280 {
281 Wix.ComponentRef componentRef = new Wix.ComponentRef();
282 componentRef.Id = c.Id;
283 componentGroup.AddChild(componentRef);
284 }
285 }
286 }
287
288 /// <summary>
289 /// Index an element.
290 /// </summary>
291 /// <param name="element">The element to index.</param>
292 private void IndexElement(Wix.ISchemaElement element)
293 {
294 if (element is Wix.Component)
295 {
296 this.components.Add(element);
297 }
298 else if (element is Wix.ComponentGroup)
299 {
300 this.componentGroups.Add(element);
301 }
302 else if (element is Wix.Directory)
303 {
304 this.directories.Add(element);
305 }
306 else if (element is Wix.DirectoryRef)
307 {
308 this.directoryRefs.Add(element);
309 }
310 else if (element is Wix.Feature)
311 {
312 this.features.Add(element);
313 }
314 else if (element is Wix.File)
315 {
316 this.files.Add(element);
317 }
318 else if (element is Wix.Module || element is Wix.PatchCreation || element is Wix.Package)
319 {
320 Debug.Assert(null == this.rootElement);
321 this.rootElement = (Wix.IParentElement)element;
322 }
323
324 // index the child elements
325 if (element is Wix.IParentElement)
326 {
327 foreach (Wix.ISchemaElement childElement in ((Wix.IParentElement)element).Children)
328 {
329 this.IndexElement(childElement);
330 }
331 }
332 }
333
334 /// <summary>
335 /// Mutate the components.
336 /// </summary>
337 private void MutateComponents()
338 {
339 IdentifierGenerator identifierGenerator = new IdentifierGenerator("Component", this.Core);
340 if (TemplateType.Module == this.templateType)
341 {
342 identifierGenerator.MaxIdentifierLength = IdentifierGenerator.MaxModuleIdentifierLength;
343 }
344
345 foreach (Wix.Component component in this.components)
346 {
347 if (null == component.Id)
348 {
349 string firstFileId = string.Empty;
350
351 // attempt to create a possible identifier from the first file identifier in the component
352 foreach (Wix.File file in component[typeof(Wix.File)])
353 {
354 firstFileId = file.Id;
355 break;
356 }
357
358 if (string.IsNullOrEmpty(firstFileId))
359 {
360 firstFileId = this.GetGuid();
361 }
362
363 component.Id = identifierGenerator.GetIdentifier(firstFileId);
364 }
365
366 if (null == component.Guid)
367 {
368 if (this.AutogenerateGuids)
369 {
370 component.Guid = "*";
371 }
372 else
373 {
374 component.Guid = this.GetGuid();
375 }
376 }
377
378 if (this.createFragments && component.ParentElement is Wix.Directory)
379 {
380 Wix.Directory directory = (Wix.Directory)component.ParentElement;
381
382 // parent directory must have an identifier to create a reference to it
383 if (null == directory.Id)
384 {
385 break;
386 }
387
388 if (this.rootElement is Wix.Module)
389 {
390 // add a ComponentRef for the Component
391 Wix.ComponentRef componentRef = new Wix.ComponentRef();
392 componentRef.Id = component.Id;
393 this.rootElement.AddChild(componentRef);
394 }
395
396 // create a new Fragment
397 Wix.Fragment fragment = new Wix.Fragment();
398 this.fragments.Add(String.Concat("Component:", (null != component.Id ? component.Id : this.fragments.Count.ToString())), fragment);
399
400 // create a new DirectoryRef
401 Wix.DirectoryRef directoryRef = new Wix.DirectoryRef();
402 directoryRef.Id = directory.Id;
403 fragment.AddChild(directoryRef);
404
405 // move the Component from the the Directory to the DirectoryRef
406 directory.RemoveChild(component);
407 directoryRef.AddChild(component);
408 }
409 }
410 }
411
412 /// <summary>
413 /// Mutate the directories.
414 /// </summary>
415 private void MutateDirectories()
416 {
417 if (!this.setUniqueIdentifiers)
418 {
419 // assign all identifiers before fragmenting (because fragmenting requires them all to be present)
420 IdentifierGenerator identifierGenerator = new IdentifierGenerator("Directory", this.Core);
421 if (TemplateType.Module == this.templateType)
422 {
423 identifierGenerator.MaxIdentifierLength = IdentifierGenerator.MaxModuleIdentifierLength;
424 }
425
426 foreach (Wix.Directory directory in this.directories)
427 {
428 if (null == directory.Id)
429 {
430 directory.Id = identifierGenerator.GetIdentifier(directory.Name);
431 }
432 }
433 }
434
435 if (this.createFragments)
436 {
437 foreach (Wix.Directory directory in this.directories)
438 {
439 if (directory.ParentElement is Wix.Directory)
440 {
441 Wix.Directory parentDirectory = (Wix.Directory)directory.ParentElement;
442
443 // parent directory must have an identifier to create a reference to it
444 if (null == parentDirectory.Id)
445 {
446 return;
447 }
448
449 // create a new Fragment
450 Wix.Fragment fragment = new Wix.Fragment();
451 this.fragments.Add(String.Concat("Directory:", ("TARGETDIR" == directory.Id ? null : (null != directory.Id ? directory.Id : this.fragments.Count.ToString()))), fragment);
452
453 // create a new DirectoryRef
454 Wix.DirectoryRef directoryRef = new Wix.DirectoryRef();
455 directoryRef.Id = parentDirectory.Id;
456 fragment.AddChild(directoryRef);
457
458 // move the Directory from the parent Directory to DirectoryRef
459 parentDirectory.RemoveChild(directory);
460 directoryRef.AddChild(directory);
461 }
462 else if (directory.ParentElement is Wix.Fragment)
463 {
464 // When creating fragments, remove any top-level Directory elements;
465 // the fragments should be pulled in by their DirectoryRefs instead.
466 Wix.Fragment parent = (Wix.Fragment)directory.ParentElement;
467 parent.RemoveChild(directory);
468
469 // Remove the fragment if it is empty.
470 if (parent.Children.GetEnumerator().Current == null && parent.ParentElement != null)
471 {
472 ((Wix.IParentElement)parent.ParentElement).RemoveChild(parent);
473 }
474 }
475 else if (directory.ParentElement == this.rootElement)
476 {
477 // create a new Fragment
478 Wix.Fragment fragment = new Wix.Fragment();
479 this.fragments.Add(String.Concat("Directory:", ("TARGETDIR" == directory.Id ? null : (null != directory.Id ? directory.Id : this.fragments.Count.ToString()))), fragment);
480
481 // move the Directory from the root element to the Fragment
482 this.rootElement.RemoveChild(directory);
483 fragment.AddChild(directory);
484 }
485 }
486 }
487 }
488
489 /// <summary>
490 /// Mutate the files.
491 /// </summary>
492 private void MutateFiles()
493 {
494 IdentifierGenerator identifierGenerator = new IdentifierGenerator("File", this.Core);
495 if (TemplateType.Module == this.templateType)
496 {
497 identifierGenerator.MaxIdentifierLength = IdentifierGenerator.MaxModuleIdentifierLength;
498 }
499
500 foreach (Wix.File file in this.files)
501 {
502 if (null == file.Id)
503 {
504 file.Id = identifierGenerator.GetIdentifier(Path.GetFileName(file.Source));
505 }
506 }
507 }
508
509 /// <summary>
510 /// Mutate a Wix element.
511 /// </summary>
512 /// <param name="wix">The Wix element to mutate.</param>
513 private void MutateWix(Wix.Wix wix)
514 {
515 if (TemplateType.Fragment != this.templateType)
516 {
517 if (null != this.rootElement || 0 != this.features.Count)
518 {
519 throw new Exception("The template option cannot be used with Feature, Package, or Module elements present.");
520 }
521
522 // create a package element although it won't always be used
523 Wix.SummaryInformation package = new Wix.SummaryInformation();
524 if (TemplateType.Module == this.templateType)
525 {
526 package.Id = this.GetGuid();
527 }
528 else
529 {
530 package.Compressed = Wix.YesNoType.yes;
531 }
532
533 package.InstallerVersion = 200;
534
535 Wix.Directory targetDir = new Wix.Directory();
536 targetDir.Id = "TARGETDIR";
537 targetDir.Name = "SourceDir";
538
539 foreach (Wix.DirectoryRef directoryRef in this.directoryRefs)
540 {
541 if (String.Equals(directoryRef.Id, "TARGETDIR", StringComparison.OrdinalIgnoreCase))
542 {
543 Wix.IParentElement parent = directoryRef.ParentElement as Wix.IParentElement;
544
545 foreach (Wix.ISchemaElement element in directoryRef.Children)
546 {
547 targetDir.AddChild(element);
548 }
549
550 parent.RemoveChild(directoryRef);
551
552 if (null != ((Wix.ISchemaElement)parent).ParentElement)
553 {
554 int i = 0;
555
556 foreach (Wix.ISchemaElement element in parent.Children)
557 {
558 i++;
559 }
560
561 if (0 == i)
562 {
563 Wix.IParentElement supParent = (Wix.IParentElement)((Wix.ISchemaElement)parent).ParentElement;
564 supParent.RemoveChild((Wix.ISchemaElement)parent);
565 }
566 }
567
568 break;
569 }
570 }
571
572 if (TemplateType.Module == this.templateType)
573 {
574 Wix.Module module = new Wix.Module();
575 module.Id = "PUT-MODULE-NAME-HERE";
576 module.Language = "1033";
577 module.Version = "1.0.0.0";
578
579 package.Manufacturer = "PUT-COMPANY-NAME-HERE";
580 module.AddChild(package);
581 module.AddChild(targetDir);
582
583 wix.AddChild(module);
584 this.rootElement = module;
585 }
586 else // product
587 {
588 Wix.Package product = new Wix.Package();
589 product.Id = this.GetGuid();
590 product.Language = "1033";
591 product.Manufacturer = "PUT-COMPANY-NAME-HERE";
592 product.Name = "PUT-PRODUCT-NAME-HERE";
593 product.UpgradeCode = this.GetGuid();
594 product.Version = "1.0.0.0";
595 product.AddChild(package);
596 product.AddChild(targetDir);
597
598 Wix.Media media = new Wix.Media();
599 media.Id = "1";
600 media.Cabinet = "product.cab";
601 media.EmbedCab = Wix.YesNoType.yes;
602 product.AddChild(media);
603
604 Wix.Feature feature = new Wix.Feature();
605 feature.Id = "ProductFeature";
606 feature.Title = "PUT-FEATURE-TITLE-HERE";
607 feature.Level = 1;
608 product.AddChild(feature);
609 this.features.Add(feature);
610
611 wix.AddChild(product);
612 this.rootElement = product;
613 }
614 }
615 }
616
617 /// <summary>
618 /// Get a generated guid or a placeholder for a guid.
619 /// </summary>
620 /// <returns>A generated guid or placeholder.</returns>
621 private string GetGuid()
622 {
623 if (this.generateGuids)
624 {
625 return Guid.NewGuid().ToString(this.guidFormat, CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture);
626 }
627 else
628 {
629 return "PUT-GUID-HERE";
630 }
631 }
632 }
633}