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