aboutsummaryrefslogtreecommitdiff
path: root/src/tools/heat/UtilFinalizeHarvesterMutator.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/heat/UtilFinalizeHarvesterMutator.cs')
-rw-r--r--src/tools/heat/UtilFinalizeHarvesterMutator.cs1220
1 files changed, 0 insertions, 1220 deletions
diff --git a/src/tools/heat/UtilFinalizeHarvesterMutator.cs b/src/tools/heat/UtilFinalizeHarvesterMutator.cs
deleted file mode 100644
index 7097759b..00000000
--- a/src/tools/heat/UtilFinalizeHarvesterMutator.cs
+++ /dev/null
@@ -1,1220 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Harvesters
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Collections.Specialized;
9 using System.Globalization;
10 using System.IO;
11 using System.Runtime.InteropServices;
12 using System.Text;
13 using WixToolset.Harvesters.Data;
14 using WixToolset.Harvesters.Extensibility;
15 using Wix = WixToolset.Harvesters.Serialize;
16
17 /// <summary>
18 /// The finalize harvester mutator for the WiX Toolset Utility Extension.
19 /// </summary>
20 public sealed class UtilFinalizeHarvesterMutator : BaseMutatorExtension
21 {
22 private ArrayList components;
23 private ArrayList directories;
24 private SortedList directoryPaths;
25 private Hashtable filePaths;
26 private ArrayList files;
27 private ArrayList registryValues;
28 private List<Wix.Payload> payloads;
29 private bool suppressCOMElements;
30 private bool suppressVB6COMElements;
31 private string preprocessorVariable;
32
33 /// <summary>
34 /// Instantiate a new UtilFinalizeHarvesterMutator.
35 /// </summary>
36 public UtilFinalizeHarvesterMutator()
37 {
38 this.components = new ArrayList();
39 this.directories = new ArrayList();
40 this.directoryPaths = new SortedList();
41 this.filePaths = new Hashtable();
42 this.files = new ArrayList();
43 this.registryValues = new ArrayList();
44 this.payloads = new List<Wix.Payload>();
45 }
46
47 /// <summary>
48 /// Gets or sets the preprocessor variable for substitution.
49 /// </summary>
50 /// <value>The preprocessor variable for substitution.</value>
51 public string PreprocessorVariable
52 {
53 get { return this.preprocessorVariable; }
54 set { this.preprocessorVariable = value; }
55 }
56
57 /// <summary>
58 /// Gets the sequence of the extension.
59 /// </summary>
60 /// <value>The sequence of the extension.</value>
61 public override int Sequence
62 {
63 get { return 2000; }
64 }
65
66 /// <summary>
67 /// Gets or sets the option to suppress COM elements.
68 /// </summary>
69 /// <value>The option to suppress COM elements.</value>
70 public bool SuppressCOMElements
71 {
72 get { return this.suppressCOMElements; }
73 set { this.suppressCOMElements = value; }
74 }
75
76 /// <summary>
77 /// Gets or sets the option to suppress VB6 COM elements.
78 /// </summary>
79 /// <value>The option to suppress VB6 COM elements.</value>
80 public bool SuppressVB6COMElements
81 {
82 get { return this.suppressVB6COMElements; }
83 set { this.suppressVB6COMElements = value; }
84 }
85
86 /// <summary>
87 /// Mutate a WiX document.
88 /// </summary>
89 /// <param name="wix">The Wix document element.</param>
90 public override void Mutate(Wix.Wix wix)
91 {
92 this.components.Clear();
93 this.directories.Clear();
94 this.directoryPaths.Clear();
95 this.filePaths.Clear();
96 this.files.Clear();
97 this.registryValues.Clear();
98 this.payloads.Clear();
99
100 // index elements in this wix document
101 this.IndexElement(wix);
102
103 this.MutateDirectories();
104 this.MutateFiles();
105 this.MutateRegistryValues();
106 this.MutatePayloads();
107
108 // must occur after all the registry values have been formatted
109 this.MutateComponents();
110 }
111
112 /// <summary>
113 /// Index an element.
114 /// </summary>
115 /// <param name="element">The element to index.</param>
116 private void IndexElement(Wix.ISchemaElement element)
117 {
118 if (element is Wix.Component)
119 {
120 // Component elements only need to be indexed if COM registry values will be strongly typed
121 if (!this.suppressCOMElements)
122 {
123 this.components.Add(element);
124 }
125 }
126 else if (element is Wix.Directory)
127 {
128 this.directories.Add(element);
129 }
130 else if (element is Wix.File)
131 {
132 this.files.Add(element);
133 }
134 else if (element is Wix.RegistryValue)
135 {
136 this.registryValues.Add(element);
137 }
138 else if (element is Wix.Payload payloadElement)
139 {
140 this.payloads.Add(payloadElement);
141 }
142
143 // index the child elements
144 if (element is Wix.IParentElement)
145 {
146 foreach (Wix.ISchemaElement childElement in ((Wix.IParentElement)element).Children)
147 {
148 this.IndexElement(childElement);
149 }
150 }
151 }
152
153 /// <summary>
154 /// Mutate the components.
155 /// </summary>
156 private void MutateComponents()
157 {
158 if (this.suppressVB6COMElements)
159 {
160 // Search for VB6 specific COM registrations
161 foreach (Wix.Component component in this.components)
162 {
163 ArrayList vb6RegistryValues = new ArrayList();
164
165 foreach (Wix.RegistryValue registryValue in component[typeof(Wix.RegistryValue)])
166 {
167 if (Wix.RegistryValue.ActionType.write == registryValue.Action && Wix.RegistryRootType.HKCR == registryValue.Root)
168 {
169 string[] parts = registryValue.Key.Split('\\');
170
171 if (String.Equals(parts[0], "CLSID", StringComparison.OrdinalIgnoreCase))
172 {
173 // Search for the VB6 CLSID {D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}
174 if (2 <= parts.Length)
175 {
176 if (String.Equals(parts[1], "{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}", StringComparison.OrdinalIgnoreCase))
177 {
178 if (!vb6RegistryValues.Contains(registryValue))
179 {
180 vb6RegistryValues.Add(registryValue);
181 }
182 }
183 }
184 }
185 else if (String.Equals(parts[0], "TypeLib", StringComparison.OrdinalIgnoreCase))
186 {
187 // Search for the VB6 TypeLibs {EA544A21-C82D-11D1-A3E4-00A0C90AEA82} or {000204EF-0000-0000-C000-000000000046}
188 if (2 <= parts.Length)
189 {
190 if (String.Equals(parts[1], "{EA544A21-C82D-11D1-A3E4-00A0C90AEA82}", StringComparison.OrdinalIgnoreCase) ||
191 String.Equals(parts[1], "{000204EF-0000-0000-C000-000000000046}", StringComparison.OrdinalIgnoreCase))
192 {
193 if (!vb6RegistryValues.Contains(registryValue))
194 {
195 vb6RegistryValues.Add(registryValue);
196 }
197 }
198 }
199 }
200 else if (String.Equals(parts[0], "Interface", StringComparison.OrdinalIgnoreCase))
201 {
202 // Search for any Interfaces that reference the VB6 TypeLibs {EA544A21-C82D-11D1-A3E4-00A0C90AEA82} or {000204EF-0000-0000-C000-000000000046}
203 if (3 <= parts.Length)
204 {
205 if (String.Equals(parts[2], "TypeLib", StringComparison.OrdinalIgnoreCase))
206 {
207 if (String.Equals(registryValue.Value, "{EA544A21-C82D-11D1-A3E4-00A0C90AEA82}", StringComparison.OrdinalIgnoreCase) ||
208 String.Equals(registryValue.Value, "{000204EF-0000-0000-C000-000000000046}", StringComparison.OrdinalIgnoreCase))
209 {
210 // Having found a match we have to loop through again finding the matching Interface entries
211 foreach (Wix.RegistryValue regValue in component[typeof(Wix.RegistryValue)])
212 {
213 if (Wix.RegistryValue.ActionType.write == regValue.Action && Wix.RegistryRootType.HKCR == regValue.Root)
214 {
215 string[] rvparts = regValue.Key.Split('\\');
216 if (String.Equals(rvparts[0], "Interface", StringComparison.OrdinalIgnoreCase))
217 {
218 if (2 <= rvparts.Length)
219 {
220 if (String.Equals(rvparts[1], parts[1], StringComparison.OrdinalIgnoreCase))
221 {
222 if (!vb6RegistryValues.Contains(regValue))
223 {
224 vb6RegistryValues.Add(regValue);
225 }
226 }
227 }
228 }
229 }
230 }
231 }
232 }
233 }
234 }
235 }
236 }
237
238 // Remove all the VB6 specific COM registry values
239 foreach (Object entry in vb6RegistryValues)
240 {
241 component.RemoveChild((Wix.RegistryValue)entry);
242 }
243 }
244 }
245
246 foreach (Wix.Component component in this.components)
247 {
248 SortedList indexedElements = CollectionsUtil.CreateCaseInsensitiveSortedList();
249 SortedList indexedRegistryValues = CollectionsUtil.CreateCaseInsensitiveSortedList();
250 List<Wix.RegistryValue> duplicateRegistryValues = new List<Wix.RegistryValue>();
251
252 // index all the File elements
253 foreach (Wix.File file in component[typeof(Wix.File)])
254 {
255 indexedElements.Add(String.Concat("file/", file.Id), file);
256 }
257
258 // group all the registry values by the COM element they would correspond to and
259 // create a COM element for each group
260 foreach (Wix.RegistryValue registryValue in component[typeof(Wix.RegistryValue)])
261 {
262 if (!String.IsNullOrEmpty(registryValue.Key) && Wix.RegistryValue.ActionType.write == registryValue.Action && Wix.RegistryRootType.HKCR == registryValue.Root && Wix.RegistryValue.TypeType.@string == registryValue.Type)
263 {
264 string index = null;
265 string[] parts = registryValue.Key.Split('\\');
266
267 // create a COM element for COM registration and index it
268 if (1 <= parts.Length)
269 {
270 if (String.Equals(parts[0], "AppID", StringComparison.OrdinalIgnoreCase))
271 {
272 // only work with GUID AppIds here
273 if (2 <= parts.Length && parts[1].StartsWith("{", StringComparison.Ordinal) && parts[1].EndsWith("}", StringComparison.Ordinal))
274 {
275 index = String.Concat(parts[0], '/', parts[1]);
276
277 if (!indexedElements.Contains(index))
278 {
279 Wix.AppId appId = new Wix.AppId();
280 appId.Id = parts[1].ToUpper(CultureInfo.InvariantCulture);
281 indexedElements.Add(index, appId);
282 }
283 }
284 }
285 else if (String.Equals(parts[0], "CLSID", StringComparison.OrdinalIgnoreCase))
286 {
287 if (2 <= parts.Length)
288 {
289 index = String.Concat(parts[0], '/', parts[1]);
290
291 if (!indexedElements.Contains(index))
292 {
293 Wix.Class wixClass = new Wix.Class();
294 wixClass.Id = parts[1].ToUpper(CultureInfo.InvariantCulture);
295 indexedElements.Add(index, wixClass);
296 }
297 }
298 }
299 else if (String.Equals(parts[0], "Component Categories", StringComparison.OrdinalIgnoreCase))
300 {
301 // If this is the .NET Component Category it should not end up in the authoring. Therefore, add
302 // the registry key to the duplicate list to ensure it gets removed later.
303 if (String.Equals(parts[1], "{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}", StringComparison.OrdinalIgnoreCase))
304 {
305 duplicateRegistryValues.Add(registryValue);
306 }
307 else
308 {
309 // TODO: add support for Component Categories to the compiler.
310 }
311 }
312 else if (String.Equals(parts[0], "Interface", StringComparison.OrdinalIgnoreCase))
313 {
314 if (2 <= parts.Length)
315 {
316 index = String.Concat(parts[0], '/', parts[1]);
317
318 if (!indexedElements.Contains(index))
319 {
320 Wix.Interface wixInterface = new Wix.Interface();
321 wixInterface.Id = parts[1].ToUpper(CultureInfo.InvariantCulture);
322 indexedElements.Add(index, wixInterface);
323 }
324 }
325 }
326 else if (String.Equals(parts[0], "TypeLib", StringComparison.Ordinal))
327 {
328 if (3 <= parts.Length)
329 {
330 // use a special index to ensure progIds are processed before classes
331 index = String.Concat(".typelib/", parts[1], '/', parts[2]);
332
333 if (!indexedElements.Contains(index))
334 {
335 Version version = TypeLibraryHarvester.ParseHexVersion(parts[2]);
336 if (version != null)
337 {
338 Wix.TypeLib typeLib = new Wix.TypeLib();
339 typeLib.Id = parts[1].ToUpper(CultureInfo.InvariantCulture);
340 typeLib.MajorVersion = version.Major;
341 typeLib.MinorVersion = version.Minor;
342 indexedElements.Add(index, typeLib);
343 }
344 else // not a valid type library registry value
345 {
346 index = null;
347 }
348 }
349 }
350 }
351 else if (parts[0].StartsWith(".", StringComparison.Ordinal))
352 {
353 // extension
354 }
355 else // ProgId (hopefully)
356 {
357 // use a special index to ensure progIds are processed before classes
358 index = String.Concat(".progid/", parts[0]);
359
360 if (!indexedElements.Contains(index))
361 {
362 Wix.ProgId progId = new Wix.ProgId();
363 progId.Id = parts[0];
364 indexedElements.Add(index, progId);
365 }
366 }
367 }
368
369 // index the RegistryValue element according to the COM element it corresponds to
370 if (null != index)
371 {
372 SortedList registryValues = (SortedList)indexedRegistryValues[index];
373
374 if (null == registryValues)
375 {
376 registryValues = CollectionsUtil.CreateCaseInsensitiveSortedList();
377 indexedRegistryValues.Add(index, registryValues);
378 }
379
380 try
381 {
382 registryValues.Add(String.Concat(registryValue.Key, '/', registryValue.Name), registryValue);
383 }
384 catch (ArgumentException)
385 {
386 duplicateRegistryValues.Add(registryValue);
387
388 if (String.IsNullOrEmpty(registryValue.Value))
389 {
390 this.Core.Messaging.Write(HarvesterWarnings.DuplicateDllRegistryEntry(String.Concat(registryValue.Key, '/', registryValue.Name), component.Id));
391 }
392 else
393 {
394 this.Core.Messaging.Write(HarvesterWarnings.DuplicateDllRegistryEntry(String.Concat(registryValue.Key, '/', registryValue.Name), registryValue.Value, component.Id));
395 }
396 }
397 }
398 }
399 }
400
401 foreach (Wix.RegistryValue removeRegistryEntry in duplicateRegistryValues)
402 {
403 component.RemoveChild(removeRegistryEntry);
404 }
405
406 // set various values on the COM elements from their corresponding registry values
407 Hashtable indexedProcessedRegistryValues = new Hashtable();
408 foreach (DictionaryEntry entry in indexedRegistryValues)
409 {
410 Wix.ISchemaElement element = (Wix.ISchemaElement)indexedElements[entry.Key];
411 string parentIndex = null;
412 SortedList registryValues = (SortedList)entry.Value;
413
414 // element-specific variables (for really tough situations)
415 string classAppId = null;
416 bool threadingModelSet = false;
417
418 foreach (Wix.RegistryValue registryValue in registryValues.Values)
419 {
420 string[] parts = registryValue.Key.ToLower(CultureInfo.InvariantCulture).Split('\\');
421 bool processed = false;
422
423 if (element is Wix.AppId)
424 {
425 Wix.AppId appId = (Wix.AppId)element;
426
427 if (2 == parts.Length)
428 {
429 if (null == registryValue.Name)
430 {
431 appId.Description = registryValue.Value;
432 processed = true;
433 }
434 }
435 }
436 else if (element is Wix.Class)
437 {
438 Wix.Class wixClass = (Wix.Class)element;
439
440 if (2 == parts.Length)
441 {
442 if (null == registryValue.Name)
443 {
444 wixClass.Description = registryValue.Value;
445 processed = true;
446 }
447 else if (String.Equals(registryValue.Name, "AppID", StringComparison.OrdinalIgnoreCase))
448 {
449 classAppId = registryValue.Value;
450 processed = true;
451 }
452 }
453 else if (3 == parts.Length)
454 {
455 Wix.Class.ContextType contextType = Wix.Class.ContextType.None;
456
457 switch (parts[2])
458 {
459 case "control":
460 wixClass.Control = Wix.YesNoType.yes;
461 processed = true;
462 break;
463 case "inprochandler":
464 if (null == registryValue.Name)
465 {
466 if (null == wixClass.Handler)
467 {
468 wixClass.Handler = "1";
469 processed = true;
470 }
471 else if ("2" == wixClass.Handler)
472 {
473 wixClass.Handler = "3";
474 processed = true;
475 }
476 }
477 break;
478 case "inprochandler32":
479 if (null == registryValue.Name)
480 {
481 if (null == wixClass.Handler)
482 {
483 wixClass.Handler = "2";
484 processed = true;
485 }
486 else if ("1" == wixClass.Handler)
487 {
488 wixClass.Handler = "3";
489 processed = true;
490 }
491 }
492 break;
493 case "inprocserver":
494 contextType = Wix.Class.ContextType.InprocServer;
495 break;
496 case "inprocserver32":
497 contextType = Wix.Class.ContextType.InprocServer32;
498 break;
499 case "insertable":
500 wixClass.Insertable = Wix.YesNoType.yes;
501 processed = true;
502 break;
503 case "localserver":
504 contextType = Wix.Class.ContextType.LocalServer;
505 break;
506 case "localserver32":
507 contextType = Wix.Class.ContextType.LocalServer32;
508 break;
509 case "progid":
510 if (null == registryValue.Name)
511 {
512 Wix.ProgId progId = (Wix.ProgId)indexedElements[String.Concat(".progid/", registryValue.Value)];
513
514 // verify that the versioned ProgId appears under this Class element
515 // if not, toss the entire element
516 if (null == progId || wixClass != progId.ParentElement)
517 {
518 element = null;
519 }
520 else
521 {
522 processed = true;
523 }
524 }
525 break;
526 case "programmable":
527 wixClass.Programmable = Wix.YesNoType.yes;
528 processed = true;
529 break;
530 case "typelib":
531 if (null == registryValue.Name)
532 {
533 foreach (DictionaryEntry indexedEntry in indexedElements)
534 {
535 string key = (string)indexedEntry.Key;
536 Wix.ISchemaElement possibleTypeLib = (Wix.ISchemaElement)indexedEntry.Value;
537
538 if (key.StartsWith(".typelib/", StringComparison.Ordinal) &&
539 0 == String.Compare(key, 9, registryValue.Value, 0, registryValue.Value.Length, StringComparison.OrdinalIgnoreCase))
540 {
541 // ensure the TypeLib is nested under the same thing we want the Class under
542 if (null == parentIndex || indexedElements[parentIndex] == possibleTypeLib.ParentElement)
543 {
544 parentIndex = key;
545 processed = true;
546 }
547 }
548 }
549 }
550 break;
551 case "version":
552 if (null == registryValue.Name)
553 {
554 wixClass.Version = registryValue.Value;
555 processed = true;
556 }
557 break;
558 case "versionindependentprogid":
559 if (null == registryValue.Name)
560 {
561 Wix.ProgId progId = (Wix.ProgId)indexedElements[String.Concat(".progid/", registryValue.Value)];
562
563 // verify that the version independent ProgId appears somewhere
564 // under this Class element - if not, toss the entire element
565 if (null == progId || wixClass != progId.ParentElement)
566 {
567 // check the parent of the parent
568 if (null == progId || null == progId.ParentElement || wixClass != progId.ParentElement.ParentElement)
569 {
570 element = null;
571 }
572 }
573
574 processed = true;
575 }
576 break;
577 }
578
579 if (Wix.Class.ContextType.None != contextType)
580 {
581 wixClass.Context |= contextType;
582
583 if (null == registryValue.Name)
584 {
585 if ((registryValue.Value.StartsWith("[!", StringComparison.Ordinal) || registryValue.Value.StartsWith("[#", StringComparison.Ordinal))
586 && registryValue.Value.EndsWith("]", StringComparison.Ordinal))
587 {
588 parentIndex = String.Concat("file/", registryValue.Value.Substring(2, registryValue.Value.Length - 3));
589 processed = true;
590 }
591 else if (String.Equals(Path.GetFileName(registryValue.Value), "mscoree.dll", StringComparison.OrdinalIgnoreCase))
592 {
593 wixClass.ForeignServer = "mscoree.dll";
594 processed = true;
595 }
596 else if (String.Equals(Path.GetFileName(registryValue.Value), "msvbvm60.dll", StringComparison.OrdinalIgnoreCase))
597 {
598 wixClass.ForeignServer = "msvbvm60.dll";
599 processed = true;
600 }
601 else
602 {
603 // Some servers are specifying relative paths (which the above code doesn't find)
604 // If there's any ambiguity leave it alone and let the developer figure it out when it breaks in the compiler
605
606 bool possibleDuplicate = false;
607 string possibleParentIndex = null;
608
609 foreach (Wix.File file in this.files)
610 {
611 if (String.Equals(registryValue.Value, Path.GetFileName(file.Source), StringComparison.OrdinalIgnoreCase))
612 {
613 if (null == possibleParentIndex)
614 {
615 possibleParentIndex = String.Concat("file/", file.Id);
616 }
617 else
618 {
619 possibleDuplicate = true;
620 break;
621 }
622 }
623 }
624
625 if (!possibleDuplicate)
626 {
627 if (null == possibleParentIndex)
628 {
629 wixClass.ForeignServer = registryValue.Value;
630 processed = true;
631 }
632 else
633 {
634 parentIndex = possibleParentIndex;
635 wixClass.RelativePath = Wix.YesNoType.yes;
636 processed = true;
637 }
638 }
639 }
640 }
641 else if (String.Equals(registryValue.Name, "ThreadingModel", StringComparison.OrdinalIgnoreCase))
642 {
643 Wix.Class.ThreadingModelType threadingModel;
644
645 if (String.Equals(registryValue.Value, "apartment", StringComparison.OrdinalIgnoreCase))
646 {
647 threadingModel = Wix.Class.ThreadingModelType.apartment;
648 processed = true;
649 }
650 else if (String.Equals(registryValue.Value, "both", StringComparison.OrdinalIgnoreCase))
651 {
652 threadingModel = Wix.Class.ThreadingModelType.both;
653 processed = true;
654 }
655 else if (String.Equals(registryValue.Value, "free", StringComparison.OrdinalIgnoreCase))
656 {
657 threadingModel = Wix.Class.ThreadingModelType.free;
658 processed = true;
659 }
660 else if (String.Equals(registryValue.Value, "neutral", StringComparison.OrdinalIgnoreCase))
661 {
662 threadingModel = Wix.Class.ThreadingModelType.neutral;
663 processed = true;
664 }
665 else if (String.Equals(registryValue.Value, "rental", StringComparison.OrdinalIgnoreCase))
666 {
667 threadingModel = Wix.Class.ThreadingModelType.rental;
668 processed = true;
669 }
670 else if (String.Equals(registryValue.Value, "single", StringComparison.OrdinalIgnoreCase))
671 {
672 threadingModel = Wix.Class.ThreadingModelType.single;
673 processed = true;
674 }
675 else
676 {
677 continue;
678 }
679
680 if (!threadingModelSet || wixClass.ThreadingModel == threadingModel)
681 {
682 wixClass.ThreadingModel = threadingModel;
683 threadingModelSet = true;
684 }
685 else
686 {
687 element = null;
688 break;
689 }
690 }
691 }
692 }
693 else if (4 == parts.Length)
694 {
695 if (String.Equals(parts[2], "implemented categories", StringComparison.Ordinal))
696 {
697 switch (parts[3])
698 {
699 case "{7dd95801-9882-11cf-9fa9-00aa006c42c4}":
700 wixClass.SafeForScripting = Wix.YesNoType.yes;
701 processed = true;
702 break;
703 case "{7dd95802-9882-11cf-9fa9-00aa006c42c4}":
704 wixClass.SafeForInitializing = Wix.YesNoType.yes;
705 processed = true;
706 break;
707 }
708 }
709 }
710 }
711 else if (element is Wix.Interface)
712 {
713 Wix.Interface wixInterface = (Wix.Interface)element;
714
715 if (2 == parts.Length && null == registryValue.Name)
716 {
717 wixInterface.Name = registryValue.Value;
718 processed = true;
719 }
720 else if (3 == parts.Length)
721 {
722 switch (parts[2])
723 {
724 case "proxystubclsid":
725 if (null == registryValue.Name)
726 {
727 wixInterface.ProxyStubClassId = registryValue.Value.ToUpper(CultureInfo.InvariantCulture);
728 processed = true;
729 }
730 break;
731 case "proxystubclsid32":
732 if (null == registryValue.Name)
733 {
734 wixInterface.ProxyStubClassId32 = registryValue.Value.ToUpper(CultureInfo.InvariantCulture);
735 processed = true;
736 }
737 break;
738 case "nummethods":
739 if (null == registryValue.Name)
740 {
741 wixInterface.NumMethods = Convert.ToInt32(registryValue.Value, CultureInfo.InvariantCulture);
742 processed = true;
743 }
744 break;
745 case "typelib":
746 if (String.Equals("Version", registryValue.Name, StringComparison.OrdinalIgnoreCase))
747 {
748 parentIndex = String.Concat(parentIndex, registryValue.Value);
749 processed = true;
750 }
751 else if (null == registryValue.Name) // TypeLib guid
752 {
753 parentIndex = String.Concat(".typelib/", registryValue.Value, '/', parentIndex);
754 processed = true;
755 }
756 break;
757 }
758 }
759 }
760 else if (element is Wix.ProgId)
761 {
762 Wix.ProgId progId = (Wix.ProgId)element;
763
764 if (null == registryValue.Name)
765 {
766 if (1 == parts.Length)
767 {
768 progId.Description = registryValue.Value;
769 processed = true;
770 }
771 else if (2 == parts.Length)
772 {
773 if (String.Equals(parts[1], "CLSID", StringComparison.OrdinalIgnoreCase))
774 {
775 parentIndex = String.Concat("CLSID/", registryValue.Value);
776 processed = true;
777 }
778 else if (String.Equals(parts[1], "CurVer", StringComparison.OrdinalIgnoreCase))
779 {
780 // If a progId points to its own ProgId with CurVer, it isn't meaningful, so ignore it
781 if (!String.Equals(progId.Id, registryValue.Value, StringComparison.OrdinalIgnoreCase))
782 {
783 // this registry value should usually be processed second so the
784 // version independent ProgId should be under the versioned one
785 parentIndex = String.Concat(".progid/", registryValue.Value);
786 processed = true;
787 }
788 }
789 }
790 }
791 }
792 else if (element is Wix.TypeLib)
793 {
794 Wix.TypeLib typeLib = (Wix.TypeLib)element;
795
796 if (null == registryValue.Name)
797 {
798 if (3 == parts.Length)
799 {
800 typeLib.Description = registryValue.Value;
801 processed = true;
802 }
803 else if (4 == parts.Length)
804 {
805 if (String.Equals(parts[3], "flags", StringComparison.OrdinalIgnoreCase))
806 {
807 int flags = Convert.ToInt32(registryValue.Value, CultureInfo.InvariantCulture);
808
809 if (0x1 == (flags & 0x1))
810 {
811 typeLib.Restricted = Wix.YesNoType.yes;
812 }
813
814 if (0x2 == (flags & 0x2))
815 {
816 typeLib.Control = Wix.YesNoType.yes;
817 }
818
819 if (0x4 == (flags & 0x4))
820 {
821 typeLib.Hidden = Wix.YesNoType.yes;
822 }
823
824 if (0x8 == (flags & 0x8))
825 {
826 typeLib.HasDiskImage = Wix.YesNoType.yes;
827 }
828
829 processed = true;
830 }
831 else if (String.Equals(parts[3], "helpdir", StringComparison.OrdinalIgnoreCase))
832 {
833 if (registryValue.Value.StartsWith("[", StringComparison.Ordinal) && (registryValue.Value.EndsWith("]", StringComparison.Ordinal)
834 || registryValue.Value.EndsWith("]\\", StringComparison.Ordinal)))
835 {
836 typeLib.HelpDirectory = registryValue.Value.Substring(1, registryValue.Value.LastIndexOf(']') - 1);
837 }
838 else if (0 == String.Compare(registryValue.Value, Environment.SystemDirectory, StringComparison.OrdinalIgnoreCase)) // VB6 DLLs register their help directory as SystemFolder
839 {
840 typeLib.HelpDirectory = "SystemFolder";
841 }
842 else if (null != component.Directory) // -sfrag has not been specified
843 {
844 typeLib.HelpDirectory = component.Directory;
845 }
846 else if (component.ParentElement is Wix.Directory) // -sfrag has been specified
847 {
848 typeLib.HelpDirectory = ((Wix.Directory)component.ParentElement).Id;
849 }
850 else if (component.ParentElement is Wix.DirectoryRef) // -sfrag has been specified
851 {
852 typeLib.HelpDirectory = ((Wix.DirectoryRef)component.ParentElement).Id;
853 }
854
855 //If the helpdir has not matched a known directory, drop it because it cannot be resolved.
856 processed = true;
857 }
858 }
859 else if (5 == parts.Length && (String.Equals("win32", parts[4], StringComparison.OrdinalIgnoreCase) || String.Equals("win64", parts[4], StringComparison.OrdinalIgnoreCase)))
860 {
861 typeLib.Language = Convert.ToInt32(parts[3], CultureInfo.InvariantCulture);
862
863 if ((registryValue.Value.StartsWith("[!", StringComparison.Ordinal) || registryValue.Value.StartsWith("[#", StringComparison.Ordinal))
864 && registryValue.Value.EndsWith("]", StringComparison.Ordinal))
865 {
866 parentIndex = String.Concat("file/", registryValue.Value.Substring(2, registryValue.Value.Length - 3));
867 }
868
869 processed = true;
870 }
871 }
872 }
873
874 // index the processed registry values by their corresponding COM element
875 if (processed)
876 {
877 indexedProcessedRegistryValues.Add(registryValue, element);
878 }
879 }
880
881 // parent the COM element
882 if (null != element)
883 {
884 if (null != parentIndex)
885 {
886 Wix.IParentElement parentElement = (Wix.IParentElement)indexedElements[parentIndex];
887
888 if (null != parentElement)
889 {
890 parentElement.AddChild(element);
891 }
892 }
893 else if (0 < indexedProcessedRegistryValues.Count)
894 {
895 component.AddChild(element);
896 }
897
898 // special handling for AppID since it doesn't fit the general model
899 if (null != classAppId)
900 {
901 Wix.AppId appId = (Wix.AppId)indexedElements[String.Concat("AppID/", classAppId)];
902
903 // move the Class element under the AppId (and put the AppId under its old parent)
904 if (null != appId)
905 {
906 // move the AppId element
907 ((Wix.IParentElement)appId.ParentElement).RemoveChild(appId);
908 ((Wix.IParentElement)element.ParentElement).AddChild(appId);
909
910 // move the Class element
911 ((Wix.IParentElement)element.ParentElement).RemoveChild(element);
912 appId.AddChild(element);
913 }
914 }
915 }
916 }
917
918 // remove the RegistryValue elements which were converted into COM elements
919 // that were successfully nested under the Component element
920 foreach (DictionaryEntry entry in indexedProcessedRegistryValues)
921 {
922 Wix.ISchemaElement element = (Wix.ISchemaElement)entry.Value;
923 Wix.RegistryValue registryValue = (Wix.RegistryValue)entry.Key;
924
925 while (null != element)
926 {
927 if (element == component)
928 {
929 ((Wix.IParentElement)registryValue.ParentElement).RemoveChild(registryValue);
930 break;
931 }
932
933 element = element.ParentElement;
934 }
935 }
936 }
937 }
938
939 /// <summary>
940 /// Mutate the directories.
941 /// </summary>
942 private void MutateDirectories()
943 {
944 foreach (Wix.Directory directory in this.directories)
945 {
946 string path = directory.FileSource;
947
948 // create a new directory element without the FileSource attribute
949 if (null != path)
950 {
951 Wix.Directory newDirectory = new Wix.Directory();
952
953 newDirectory.Id = directory.Id;
954 newDirectory.Name = directory.Name;
955
956 foreach (Wix.ISchemaElement element in directory.Children)
957 {
958 newDirectory.AddChild(element);
959 }
960
961 ((Wix.IParentElement)directory.ParentElement).AddChild(newDirectory);
962 ((Wix.IParentElement)directory.ParentElement).RemoveChild(directory);
963
964 if (null != newDirectory.Id)
965 {
966 this.directoryPaths[path.ToLower(CultureInfo.InvariantCulture)] = String.Concat("[", newDirectory.Id, "]");
967 }
968 }
969 }
970 }
971
972 /// <summary>
973 /// Mutate the files.
974 /// </summary>
975 private void MutateFiles()
976 {
977 string sourceDirSubstitution = this.preprocessorVariable;
978 if (sourceDirSubstitution != null)
979 {
980 string prefix = "$(";
981 if (sourceDirSubstitution.StartsWith("wix.", StringComparison.Ordinal))
982 {
983 prefix = "!(";
984 }
985 sourceDirSubstitution = String.Concat(prefix, sourceDirSubstitution, ")");
986 }
987
988 foreach (Wix.File file in this.files)
989 {
990 if (null != file.Id && null != file.Source)
991 {
992 string fileSource = this.Core.ResolveFilePath(file.Source);
993
994 // index the long path
995 this.filePaths[fileSource.ToLower(CultureInfo.InvariantCulture)] = String.Concat("[#", file.Id, "]");
996
997 // index the long path as a URL for assembly harvesting
998 Uri fileUri = new Uri(fileSource);
999 this.filePaths[fileUri.ToString().ToLower(CultureInfo.InvariantCulture)] = String.Concat("file:///[#", file.Id, "]");
1000
1001 // index the short path
1002 string shortPath = NativeMethods.GetShortPathName(fileSource);
1003 this.filePaths[shortPath.ToLower(CultureInfo.InvariantCulture)] = String.Concat("[!", file.Id, "]");
1004
1005 // escape literal $ characters
1006 file.Source = file.Source.Replace("$", "$$");
1007
1008 if (null != sourceDirSubstitution && file.Source.StartsWith("SourceDir\\", StringComparison.Ordinal))
1009 {
1010 file.Source = file.Source.Substring(9).Insert(0, sourceDirSubstitution);
1011 }
1012 }
1013 }
1014 }
1015
1016 /// <summary>
1017 /// Mutate the payloads.
1018 /// </summary>
1019 private void MutatePayloads()
1020 {
1021 string sourceDirSubstitution = this.preprocessorVariable;
1022 if (sourceDirSubstitution == null)
1023 {
1024 return;
1025 }
1026
1027 string prefix = "$(";
1028 if (sourceDirSubstitution.StartsWith("wix.", StringComparison.Ordinal))
1029 {
1030 prefix = "!(";
1031 }
1032 sourceDirSubstitution = String.Concat(prefix, sourceDirSubstitution, ")");
1033
1034 foreach (var payload in this.payloads)
1035 {
1036 if (payload.SourceFile != null && payload.SourceFile.StartsWith("SourceDir\\", StringComparison.Ordinal))
1037 {
1038 payload.SourceFile = payload.SourceFile.Substring(9).Insert(0, sourceDirSubstitution);
1039 }
1040 }
1041 }
1042
1043 /// <summary>
1044 /// Mutate an individual registry string, according to a collection of replacement items.
1045 /// </summary>
1046 /// <param name="value">The string to mutate.</param>
1047 /// <param name="replace">The collection of items to replace within the string.</param>
1048 /// <value>The mutated registry string.</value>
1049 private string MutateRegistryString(string value, ICollection replace)
1050 {
1051 int index;
1052 string lowercaseValue = value.ToLower(CultureInfo.InvariantCulture);
1053
1054 foreach (DictionaryEntry entry in replace)
1055 {
1056 while (0 <= (index = lowercaseValue.IndexOf((string)entry.Key, StringComparison.Ordinal)))
1057 {
1058 value = value.Remove(index, ((string)entry.Key).Length);
1059 value = value.Insert(index, (string)entry.Value);
1060 lowercaseValue = value.ToLower(CultureInfo.InvariantCulture);
1061 }
1062 }
1063
1064 return value;
1065 }
1066
1067 /// <summary>
1068 /// Mutate the registry values.
1069 /// </summary>
1070 private void MutateRegistryValues()
1071 {
1072 if (this.SuppressVB6COMElements && this.SuppressCOMElements)
1073 {
1074 var vb6RegistryValues = new List<Wix.RegistryValue>();
1075 foreach (Wix.RegistryValue registryValue in this.registryValues)
1076 {
1077 if (IsVb6RegistryValue(registryValue))
1078 {
1079 if (!vb6RegistryValues.Contains(registryValue))
1080 {
1081 vb6RegistryValues.Add(registryValue);
1082 }
1083 }
1084 }
1085
1086 // Remove all the VB6 specific COM registry values
1087 foreach (var reg in vb6RegistryValues)
1088 {
1089 if (reg.ParentElement is Wix.Component component)
1090 {
1091 component.RemoveChild(reg);
1092 }
1093 this.registryValues.Remove(reg);
1094 }
1095 }
1096
1097
1098 ArrayList reversedDirectoryPaths = new ArrayList();
1099
1100 // reverse the indexed directory paths to ensure the longest paths are found first
1101 foreach (DictionaryEntry entry in this.directoryPaths)
1102 {
1103 reversedDirectoryPaths.Insert(0, entry);
1104 }
1105
1106 foreach (Wix.RegistryValue registryValue in this.registryValues)
1107 {
1108 // Multi-string values are stored as children - their "Value" member is null
1109 if (Wix.RegistryValue.TypeType.multiString == registryValue.Type)
1110 {
1111 foreach (Wix.MultiStringValue multiStringValue in registryValue.Children)
1112 {
1113 // first replace file paths with their MSI tokens
1114 multiStringValue.Value = this.MutateRegistryString(multiStringValue.Value, (ICollection)this.filePaths);
1115 // next replace directory paths with their MSI tokens
1116 multiStringValue.Value = this.MutateRegistryString(multiStringValue.Value, (ICollection)reversedDirectoryPaths);
1117 }
1118 }
1119 else
1120 {
1121 // first replace file paths with their MSI tokens
1122 registryValue.Value = this.MutateRegistryString(registryValue.Value, (ICollection)this.filePaths);
1123 // next replace directory paths with their MSI tokens
1124 registryValue.Value = this.MutateRegistryString(registryValue.Value, (ICollection)reversedDirectoryPaths);
1125 }
1126 }
1127 }
1128
1129 private static bool IsVb6RegistryValue(Wix.RegistryValue registryValue)
1130 {
1131 if (Wix.RegistryValue.ActionType.write == registryValue.Action && Wix.RegistryRootType.HKCR == registryValue.Root)
1132 {
1133 string[] parts = registryValue.Key.Split('\\');
1134 if (String.Equals(parts[0], "CLSID", StringComparison.OrdinalIgnoreCase))
1135 {
1136 // Search for the VB6 CLSID {D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}
1137 if (2 <= parts.Length)
1138 {
1139 if (String.Equals(parts[1], "{D5DE8D20-5BB8-11D1-A1E3-00A0C90F2731}", StringComparison.OrdinalIgnoreCase))
1140 {
1141 return true;
1142 }
1143 }
1144 }
1145 else if (String.Equals(parts[0], "TypeLib", StringComparison.OrdinalIgnoreCase))
1146 {
1147 // Search for the VB6 TypeLibs {EA544A21-C82D-11D1-A3E4-00A0C90AEA82} or {000204EF-0000-0000-C000-000000000046}
1148 if (2 <= parts.Length)
1149 {
1150 if (String.Equals(parts[1], "{EA544A21-C82D-11D1-A3E4-00A0C90AEA82}", StringComparison.OrdinalIgnoreCase) ||
1151 String.Equals(parts[1], "{000204EF-0000-0000-C000-000000000046}", StringComparison.OrdinalIgnoreCase))
1152 {
1153 return true;
1154 }
1155 }
1156 }
1157 else if (String.Equals(parts[0], "Interface", StringComparison.OrdinalIgnoreCase))
1158 {
1159 // Search for any Interfaces that reference the VB6 TypeLibs {EA544A21-C82D-11D1-A3E4-00A0C90AEA82} or {000204EF-0000-0000-C000-000000000046}
1160 if (3 <= parts.Length)
1161 {
1162 if (String.Equals(parts[2], "TypeLib", StringComparison.OrdinalIgnoreCase))
1163 {
1164 if (String.Equals(registryValue.Value, "{EA544A21-C82D-11D1-A3E4-00A0C90AEA82}", StringComparison.OrdinalIgnoreCase) ||
1165 String.Equals(registryValue.Value, "{000204EF-0000-0000-C000-000000000046}", StringComparison.OrdinalIgnoreCase))
1166 {
1167 return true;
1168 }
1169 }
1170 }
1171 }
1172 }
1173 return false;
1174 }
1175
1176 /// <summary>
1177 /// The native methods for grabbing machine-specific short file paths.
1178 /// </summary>
1179 private class NativeMethods
1180 {
1181 /// <summary>
1182 /// Gets the short name for a file.
1183 /// </summary>
1184 /// <param name="fullPath">Fullpath to file on disk.</param>
1185 /// <returns>Short name for file.</returns>
1186 internal static string GetShortPathName(string fullPath)
1187 {
1188 var bufferSize = (int)GetShortPathName(fullPath, null, 0);
1189 if (0 == bufferSize)
1190 {
1191 int err = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
1192 throw new System.Runtime.InteropServices.COMException(String.Concat("Failed to get short path buffer size for file: ", fullPath), err);
1193 }
1194
1195 bufferSize += 1;
1196 var shortPath = new StringBuilder(bufferSize, bufferSize);
1197
1198 uint result = GetShortPathName(fullPath, shortPath, bufferSize);
1199
1200 if (0 == result)
1201 {
1202 int err = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
1203 throw new System.Runtime.InteropServices.COMException(String.Concat("Failed to get short path name for file: ", fullPath), err);
1204 }
1205
1206 return shortPath.ToString();
1207 }
1208
1209 /// <summary>
1210 /// Gets the short name for a file.
1211 /// </summary>
1212 /// <param name="longPath">Long path to convert to short path.</param>
1213 /// <param name="shortPath">Short path from long path.</param>
1214 /// <param name="buffer">Size of short path.</param>
1215 /// <returns>zero if success.</returns>
1216 [DllImport("kernel32.dll", EntryPoint = "GetShortPathNameW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
1217 internal static extern uint GetShortPathName(string longPath, StringBuilder shortPath, [MarshalAs(UnmanagedType.U4)]int buffer);
1218 }
1219 }
1220}