summaryrefslogtreecommitdiff
path: root/src/tools/heat/VSProjectHarvester.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/heat/VSProjectHarvester.cs')
-rw-r--r--src/tools/heat/VSProjectHarvester.cs1455
1 files changed, 1455 insertions, 0 deletions
diff --git a/src/tools/heat/VSProjectHarvester.cs b/src/tools/heat/VSProjectHarvester.cs
new file mode 100644
index 00000000..93b20cd8
--- /dev/null
+++ b/src/tools/heat/VSProjectHarvester.cs
@@ -0,0 +1,1455 @@
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.IO;
7 using System.Reflection;
8 using System.Collections;
9 using System.Collections.Generic;
10 using System.Globalization;
11 using System.Text.RegularExpressions;
12 using System.Xml;
13 using WixToolset.Data;
14 using WixToolset.Extensibility.Services;
15 using WixToolset.Harvesters.Data;
16 using WixToolset.Harvesters.Extensibility;
17 using Wix = WixToolset.Harvesters.Serialize;
18
19 /// <summary>
20 /// Harvest WiX authoring for the outputs of a VS project.
21 /// </summary>
22 public sealed class VSProjectHarvester : BaseHarvesterExtension
23 {
24 // These format strings are used for generated element identifiers.
25 // {0} = project name
26 // {1} = POG name
27 // {2} = file name
28 private const string DirectoryIdFormat = "{0}.{1}";
29 private const string ComponentIdFormat = "{0}.{1}.{2}";
30 private const string FileIdFormat = "{0}.{1}.{2}";
31 private const string VariableFormat = "$(var.{0}.{1})";
32 private const string WixVariableFormat = "!(wix.{0}.{1})";
33
34 private const string ComponentPrefix = "cmp";
35 private const string DirectoryPrefix = "dir";
36 private const string FilePrefix = "fil";
37
38 private string projectGUID;
39 private string directoryIds;
40 private string directoryRefSeed;
41 private string projectName;
42 private string configuration;
43 private string platform;
44 private bool setUniqueIdentifiers;
45 private GenerateType generateType;
46 private bool generateWixVars;
47
48
49 private static readonly ProjectOutputGroup[] allOutputGroups = new ProjectOutputGroup[]
50 {
51 new ProjectOutputGroup("Binaries", "BuiltProjectOutputGroup", "TargetDir"),
52 new ProjectOutputGroup("Symbols", "DebugSymbolsProjectOutputGroup", "TargetDir"),
53 new ProjectOutputGroup("Documents", "DocumentationProjectOutputGroup", "ProjectDir"),
54 new ProjectOutputGroup("Satellites", "SatelliteDllsProjectOutputGroup", "TargetDir"),
55 new ProjectOutputGroup("Sources", "SourceFilesProjectOutputGroup", "ProjectDir"),
56 new ProjectOutputGroup("Content", "ContentFilesProjectOutputGroup", "ProjectDir"),
57 };
58
59 private string[] outputGroups;
60
61 /// <summary>
62 /// Instantiate a new VSProjectHarvester.
63 /// </summary>
64 /// <param name="outputGroups">List of project output groups to harvest.</param>
65 public VSProjectHarvester(string[] outputGroups)
66 {
67 if (outputGroups == null)
68 {
69 throw new ArgumentNullException("outputGroups");
70 }
71
72 this.outputGroups = outputGroups;
73 }
74
75 /// <summary>
76 /// Gets or sets the configuration to set when harvesting.
77 /// </summary>
78 /// <value>The configuration to set when harvesting.</value>
79 public string Configuration
80 {
81 get { return this.configuration; }
82 set { this.configuration = value; }
83 }
84
85 public string DirectoryIds
86 {
87 get { return this.directoryIds; }
88 set { this.directoryIds = value; }
89 }
90
91 /// <summary>
92 /// Gets or sets what type of elements are to be generated.
93 /// </summary>
94 /// <value>The type of elements being generated.</value>
95 public GenerateType GenerateType
96 {
97 get { return this.generateType; }
98 set { this.generateType = value; }
99 }
100
101 /// <summary>
102 /// Gets or sets whether or not to use wix variables.
103 /// </summary>
104 /// <value>Whether or not to use wix variables.</value>
105 public bool GenerateWixVars
106 {
107 get { return this.generateWixVars; }
108 set { this.generateWixVars = value; }
109 }
110
111 /// <summary>
112 /// Gets or sets the location to load MSBuild from.
113 /// </summary>
114 public string MsbuildBinPath { get; set; }
115
116 /// <summary>
117 /// Gets or sets the platform to set when harvesting.
118 /// </summary>
119 /// <value>The platform to set when harvesting.</value>
120 public string Platform
121 {
122 get { return this.platform; }
123 set { this.platform = value; }
124 }
125
126 /// <summary>
127 /// Gets or sets the project name to use in wix variables.
128 /// </summary>
129 /// <value>The project name to use in wix variables.</value>
130 public string ProjectName
131 {
132 get { return this.projectName; }
133 set { this.projectName = value; }
134 }
135
136 /// <summary>
137 /// Gets or sets the option to set unique identifiers.
138 /// </summary>
139 /// <value>The option to set unique identifiers.</value>
140 public bool SetUniqueIdentifiers
141 {
142 get { return this.setUniqueIdentifiers; }
143 set { this.setUniqueIdentifiers = value; }
144 }
145
146 /// <summary>
147 /// Gets or sets whether to ignore MsbuildBinPath when the project file specifies a known MSBuild version.
148 /// </summary>
149 public bool UseToolsVersion { get; set; }
150
151 /// <summary>
152 /// Gets a list of friendly output group names that will be recognized on the command-line.
153 /// </summary>
154 /// <returns>Array of output group names.</returns>
155 public static string[] GetOutputGroupNames()
156 {
157 string[] names = new string[VSProjectHarvester.allOutputGroups.Length];
158 for (int i = 0; i < names.Length; i++)
159 {
160 names[i] = VSProjectHarvester.allOutputGroups[i].Name;
161 }
162 return names;
163 }
164
165 /// <summary>
166 /// Harvest a VS project.
167 /// </summary>
168 /// <param name="argument">The path of the VS project file.</param>
169 /// <returns>The harvested directory.</returns>
170 public override Wix.Fragment[] Harvest(string argument)
171 {
172 if (null == argument)
173 {
174 throw new ArgumentNullException("argument");
175 }
176
177 if (!System.IO.File.Exists(argument))
178 {
179 throw new FileNotFoundException(argument);
180 }
181
182 // Match specified output group names to available POG structures
183 // and collect list of build output groups to pass to MSBuild.
184 ProjectOutputGroup[] pogs = new ProjectOutputGroup[this.outputGroups.Length];
185 string[] buildOutputGroups = new string[this.outputGroups.Length];
186 for (int i = 0; i < this.outputGroups.Length; i++)
187 {
188 foreach (ProjectOutputGroup pog in VSProjectHarvester.allOutputGroups)
189 {
190 if (pog.Name == this.outputGroups[i])
191 {
192 pogs[i] = pog;
193 buildOutputGroups[i] = pog.BuildOutputGroup;
194 }
195 }
196
197 if (buildOutputGroups[i] == null)
198 {
199 throw new WixException(HarvesterErrors.InvalidOutputGroup(this.outputGroups[i]));
200 }
201 }
202
203 string projectFile = Path.GetFullPath(argument);
204
205 IDictionary buildOutputs = this.GetProjectBuildOutputs(projectFile, buildOutputGroups);
206
207 ArrayList fragmentList = new ArrayList();
208
209 for (int i = 0; i < pogs.Length; i++)
210 {
211 this.HarvestProjectOutputGroup(projectFile, buildOutputs, pogs[i], fragmentList);
212 }
213
214 return (Wix.Fragment[]) fragmentList.ToArray(typeof(Wix.Fragment));
215 }
216
217 /// <summary>
218 /// Runs MSBuild on a project file to get the list of filenames for the specified output groups.
219 /// </summary>
220 /// <param name="projectFile">VS MSBuild project file to load.</param>
221 /// <param name="buildOutputGroups">List of MSBuild output group names.</param>
222 /// <returns>Dictionary mapping output group names to lists of filenames in the group.</returns>
223 private IDictionary GetProjectBuildOutputs(string projectFile, string[] buildOutputGroups)
224 {
225 MSBuildProject project = this.GetMsbuildProject(projectFile);
226
227 project.Load(projectFile);
228
229 IDictionary buildOutputs = new Hashtable();
230
231 string originalDirectory = System.IO.Directory.GetCurrentDirectory();
232 System.IO.Directory.SetCurrentDirectory(Path.GetDirectoryName(projectFile));
233 bool buildSuccess = false;
234 try
235 {
236 buildSuccess = project.Build(projectFile, buildOutputGroups, buildOutputs);
237 }
238 finally
239 {
240 System.IO.Directory.SetCurrentDirectory(originalDirectory);
241 }
242
243 if (!buildSuccess)
244 {
245 throw new WixException(HarvesterErrors.BuildFailed());
246 }
247
248 this.projectGUID = project.GetEvaluatedProperty("ProjectGuid");
249
250 if (null == this.projectGUID)
251 {
252 throw new WixException(HarvesterErrors.BuildFailed());
253 }
254
255 IDictionary newDictionary = new Dictionary<object, object>();
256 foreach (string buildOutput in buildOutputs.Keys)
257 {
258 IEnumerable buildOutputFiles = buildOutputs[buildOutput] as IEnumerable;
259
260 bool hasFiles = false;
261
262 foreach (object file in buildOutputFiles)
263 {
264 hasFiles = true;
265 break;
266 }
267
268 // Try the item group if no outputs
269 if (!hasFiles)
270 {
271 IEnumerable itemFiles = project.GetEvaluatedItemsByName(String.Concat(buildOutput, "Output"));
272 List<object> itemFileList = new List<object>();
273
274 // Get each BuildItem and add the file path to our list
275 foreach (object itemFile in itemFiles)
276 {
277 itemFileList.Add(project.GetBuildItem(itemFile));
278 }
279
280 // Use our list for this build output
281 newDictionary.Add(buildOutput, itemFileList);
282 }
283 else
284 {
285 newDictionary.Add(buildOutput, buildOutputFiles);
286 }
287 }
288
289 return newDictionary;
290 }
291
292 /// <summary>
293 /// Creates WiX fragments for files in one output group.
294 /// </summary>
295 /// <param name="projectFile">VS MSBuild project file.</param>
296 /// <param name="buildOutputs">Dictionary of build outputs retrieved from an MSBuild run on the project file.</param>
297 /// <param name="pog">Project output group parameters.</param>
298 /// <param name="fragmentList">List to which generated fragments will be added.</param>
299 /// <returns>Count of harvested files.</returns>
300 private int HarvestProjectOutputGroup(string projectFile, IDictionary buildOutputs, ProjectOutputGroup pog, IList fragmentList)
301 {
302 string projectName = Path.GetFileNameWithoutExtension(projectFile);
303 string projectBaseDir = null;
304
305 if (this.ProjectName != null)
306 {
307 projectName = this.ProjectName;
308 }
309
310 string sanitizedProjectName = this.Core.CreateIdentifierFromFilename(projectName);
311
312 Wix.IParentElement harvestParent;
313
314 if (this.GenerateType == GenerateType.Container)
315 {
316 Wix.Container container = new Wix.Container();
317 harvestParent = container;
318
319 container.Name = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name);
320 }
321 else if (this.GenerateType == GenerateType.PayloadGroup)
322 {
323 Wix.PayloadGroup payloadGroup = new Wix.PayloadGroup();
324 harvestParent = payloadGroup;
325
326 payloadGroup.Id = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name);
327 }
328 else if (this.GenerateType == GenerateType.PackageGroup)
329 {
330 Wix.PackageGroup packageGroup = new Wix.PackageGroup();
331 harvestParent = packageGroup;
332
333 packageGroup.Id = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name);
334 }
335 else
336 {
337 Wix.DirectoryRef directoryRef = new Wix.DirectoryRef();
338 harvestParent = directoryRef;
339
340 if (!String.IsNullOrEmpty(this.directoryIds))
341 {
342 directoryRef.Id = this.directoryIds;
343 }
344 else if (this.setUniqueIdentifiers)
345 {
346 directoryRef.Id = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name);
347 }
348 else
349 {
350 directoryRef.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name));
351 }
352
353 this.directoryRefSeed = this.Core.GenerateIdentifier(DirectoryPrefix, this.projectGUID, pog.Name);
354 }
355
356 IEnumerable pogFiles = buildOutputs[pog.BuildOutputGroup] as IEnumerable;
357 if (pogFiles == null)
358 {
359 throw new WixException(HarvesterErrors.MissingProjectOutputGroup(
360 projectFile, pog.BuildOutputGroup));
361 }
362
363 if (pog.FileSource == "ProjectDir")
364 {
365 projectBaseDir = Path.GetDirectoryName(projectFile) + "\\";
366 }
367
368 int harvestCount = this.HarvestProjectOutputGroupFiles(projectBaseDir, projectName, pog.Name, pog.FileSource, pogFiles, harvestParent);
369
370 if (this.GenerateType == GenerateType.Container)
371 {
372 // harvestParent must be a Container at this point
373 Wix.Container container = harvestParent as Wix.Container;
374
375 Wix.Fragment fragment = new Wix.Fragment();
376 fragment.AddChild(container);
377 fragmentList.Add(fragment);
378 }
379 else if (this.GenerateType == GenerateType.PackageGroup)
380 {
381 // harvestParent must be a PackageGroup at this point
382 Wix.PackageGroup packageGroup = harvestParent as Wix.PackageGroup;
383
384 Wix.Fragment fragment = new Wix.Fragment();
385 fragment.AddChild(packageGroup);
386 fragmentList.Add(fragment);
387 }
388 else if (this.GenerateType == GenerateType.PayloadGroup)
389 {
390 // harvestParent must be a Container at this point
391 Wix.PayloadGroup payloadGroup = harvestParent as Wix.PayloadGroup;
392
393 Wix.Fragment fragment = new Wix.Fragment();
394 fragment.AddChild(payloadGroup);
395 fragmentList.Add(fragment);
396 }
397 else
398 {
399 // harvestParent must be a DirectoryRef at this point
400 Wix.DirectoryRef directoryRef = harvestParent as Wix.DirectoryRef;
401
402 if (harvestCount > 0)
403 {
404 Wix.Fragment drf = new Wix.Fragment();
405 drf.AddChild(directoryRef);
406 fragmentList.Add(drf);
407 }
408
409 Wix.ComponentGroup cg = new Wix.ComponentGroup();
410
411 if (this.setUniqueIdentifiers || !String.IsNullOrEmpty(this.directoryIds))
412 {
413 cg.Id = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name);
414 }
415 else
416 {
417 cg.Id = directoryRef.Id;
418 }
419
420 if (harvestCount > 0)
421 {
422 this.AddComponentsToComponentGroup(directoryRef, cg);
423 }
424
425 Wix.Fragment cgf = new Wix.Fragment();
426 cgf.AddChild(cg);
427 fragmentList.Add(cgf);
428 }
429
430 return harvestCount;
431 }
432
433 /// <summary>
434 /// Add all Components in an element tree to a ComponentGroup.
435 /// </summary>
436 /// <param name="parent">Parent of an element tree that will be searched for Components.</param>
437 /// <param name="cg">The ComponentGroup the Components will be added to.</param>
438 private void AddComponentsToComponentGroup(Wix.IParentElement parent, Wix.ComponentGroup cg)
439 {
440 foreach (Wix.ISchemaElement childElement in parent.Children)
441 {
442 Wix.Component c = childElement as Wix.Component;
443 if (c != null)
444 {
445 Wix.ComponentRef cr = new Wix.ComponentRef();
446 cr.Id = c.Id;
447 cg.AddChild(cr);
448 }
449 else
450 {
451 Wix.IParentElement p = childElement as Wix.IParentElement;
452 if (p != null)
453 {
454 this.AddComponentsToComponentGroup(p, cg);
455 }
456 }
457 }
458 }
459
460 /// <summary>
461 /// Harvest files from one output group of a VS project.
462 /// </summary>
463 /// <param name="baseDir">The base directory of the files.</param>
464 /// <param name="projectName">Name of the project, to be used as a prefix for generated identifiers.</param>
465 /// <param name="pogName">Name of the project output group, used for generating identifiers for WiX elements.</param>
466 /// <param name="pogFileSource">The ProjectOutputGroup file source.</param>
467 /// <param name="outputGroupFiles">The files from one output group to harvest.</param>
468 /// <param name="parent">The parent element that will contain the components of the harvested files.</param>
469 /// <returns>The number of files harvested.</returns>
470 private int HarvestProjectOutputGroupFiles(string baseDir, string projectName, string pogName, string pogFileSource, IEnumerable outputGroupFiles, Wix.IParentElement parent)
471 {
472 int fileCount = 0;
473
474 Wix.ISchemaElement exeFile = null;
475 Wix.ISchemaElement dllFile = null;
476 Wix.ISchemaElement appConfigFile = null;
477
478 // Keep track of files inserted
479 // Files can have different absolute paths but get mapped to the same SourceFile
480 // after the project variables have been used. For example, a WiX project that
481 // is building multiple cultures will have many output MSIs/MSMs, but will all get
482 // mapped to $(var.ProjName.TargetDir)\ProjName.msm. These duplicates would
483 // prevent generated code from compiling.
484 Dictionary<string, bool> seenList = new Dictionary<string,bool>();
485
486 foreach (object output in outputGroupFiles)
487 {
488 string filePath = output.ToString();
489 string fileName = Path.GetFileName(filePath);
490 string fileDir = Path.GetDirectoryName(filePath);
491 string link = null;
492
493 MethodInfo getMetadataMethod = output.GetType().GetMethod("GetMetadata");
494 if (getMetadataMethod != null)
495 {
496 link = (string)getMetadataMethod.Invoke(output, new object[] { "Link" });
497 if (!String.IsNullOrEmpty(link))
498 {
499 fileDir = Path.GetDirectoryName(Path.Combine(baseDir, link));
500 }
501 }
502
503 Wix.IParentElement parentDir = parent;
504 // Ignore Containers and PayloadGroups because they do not have a nested structure.
505 if (baseDir != null && !String.Equals(Path.GetDirectoryName(baseDir), fileDir, StringComparison.OrdinalIgnoreCase)
506 && this.GenerateType != GenerateType.Container && this.GenerateType != GenerateType.PackageGroup && this.GenerateType != GenerateType.PayloadGroup)
507 {
508 Uri baseUri = new Uri(baseDir);
509 Uri relativeUri = baseUri.MakeRelativeUri(new Uri(fileDir));
510 parentDir = this.GetSubDirElement(parentDir, relativeUri);
511 }
512
513 string parentDirId = null;
514
515 if (parentDir is Wix.DirectoryRef)
516 {
517 parentDirId = this.directoryRefSeed;
518 }
519 else if (parentDir is Wix.Directory)
520 {
521 parentDirId = ((Wix.Directory)parentDir).Id;
522 }
523
524 if (this.GenerateType == GenerateType.Container || this.GenerateType == GenerateType.PayloadGroup)
525 {
526 Wix.Payload payload = new Wix.Payload();
527
528 this.HarvestProjectOutputGroupPayloadFile(baseDir, projectName, pogName, pogFileSource, filePath, fileName, link, parentDir, payload, seenList);
529 }
530 else if (this.GenerateType == GenerateType.PackageGroup)
531 {
532 this.HarvestProjectOutputGroupPackage(projectName, pogName, pogFileSource, filePath, fileName, link, parentDir, seenList);
533 }
534 else
535 {
536 Wix.Component component = new Wix.Component();
537 Wix.File file = new Wix.File();
538
539 this.HarvestProjectOutputGroupFile(baseDir, projectName, pogName, pogFileSource, filePath, fileName, link, parentDir, parentDirId, component, file, seenList);
540
541 if (String.Equals(Path.GetExtension(file.Source), ".exe", StringComparison.OrdinalIgnoreCase))
542 {
543 exeFile = file;
544 }
545 else if (String.Equals(Path.GetExtension(file.Source), ".dll", StringComparison.OrdinalIgnoreCase))
546 {
547 dllFile = file;
548 }
549 else if (file.Source.EndsWith("app.config", StringComparison.OrdinalIgnoreCase))
550 {
551 appConfigFile = file;
552 }
553 }
554
555 fileCount++;
556 }
557
558 // if there was no exe file found fallback on the dll file found
559 if (exeFile == null && dllFile != null)
560 {
561 exeFile = dllFile;
562 }
563
564 // Special case for the app.config file in the Binaries POG...
565 // The POG refers to the files in the OBJ directory, while the
566 // generated WiX code references them in the bin directory.
567 // The app.config file gets renamed to match the exe name.
568 if ("Binaries" == pogName && null != exeFile && null != appConfigFile)
569 {
570 if (appConfigFile is Wix.File)
571 {
572 Wix.File appConfigFileAsWixFile = appConfigFile as Wix.File;
573 Wix.File exeFileAsWixFile = exeFile as Wix.File;
574 // Case insensitive replace
575 appConfigFileAsWixFile.Source = Regex.Replace(appConfigFileAsWixFile.Source, @"app\.config", Path.GetFileName(exeFileAsWixFile.Source) + ".config", RegexOptions.IgnoreCase);
576 }
577 }
578
579 return fileCount;
580 }
581
582 private void HarvestProjectOutputGroupFile(string baseDir, string projectName, string pogName, string pogFileSource, string filePath, string fileName, string link, Wix.IParentElement parentDir, string parentDirId, Wix.Component component, Wix.File file, Dictionary<string, bool> seenList)
583 {
584 string varFormat = VariableFormat;
585 if (this.generateWixVars)
586 {
587 varFormat = WixVariableFormat;
588 }
589
590 if (pogName.Equals("Satellites", StringComparison.OrdinalIgnoreCase))
591 {
592 Wix.Directory locDirectory = new Wix.Directory();
593
594 locDirectory.Name = Path.GetFileName(Path.GetDirectoryName(Path.GetFullPath(filePath)));
595 file.Source = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", locDirectory.Name, "\\", Path.GetFileName(filePath));
596
597 if (!seenList.ContainsKey(file.Source))
598 {
599 parentDir.AddChild(locDirectory);
600 locDirectory.AddChild(component);
601 component.AddChild(file);
602 seenList.Add(file.Source, true);
603
604 if (this.setUniqueIdentifiers)
605 {
606 locDirectory.Id = this.Core.GenerateIdentifier(DirectoryPrefix, parentDirId, locDirectory.Name);
607 file.Id = this.Core.GenerateIdentifier(FilePrefix, locDirectory.Id, fileName);
608 component.Id = this.Core.GenerateIdentifier(ComponentPrefix, locDirectory.Id, file.Id);
609 }
610 else
611 {
612 locDirectory.Id = this.Core.CreateIdentifierFromFilename(String.Format(DirectoryIdFormat, (parentDir is Wix.DirectoryRef) ? ((Wix.DirectoryRef)parentDir).Id : parentDirId, locDirectory.Name));
613 file.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.FileIdFormat, projectName, pogName, String.Concat(locDirectory.Name, ".", fileName)));
614 component.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.ComponentIdFormat, projectName, pogName, String.Concat(locDirectory.Name, ".", fileName)));
615 }
616 }
617 }
618 else
619 {
620 file.Source = GenerateSourceFilePath(baseDir, projectName, pogFileSource, filePath, link, varFormat);
621
622 if (!seenList.ContainsKey(file.Source))
623 {
624 component.AddChild(file);
625 parentDir.AddChild(component);
626 seenList.Add(file.Source, true);
627
628 if (this.setUniqueIdentifiers)
629 {
630 file.Id = this.Core.GenerateIdentifier(FilePrefix, parentDirId, fileName);
631 component.Id = this.Core.GenerateIdentifier(ComponentPrefix, parentDirId, file.Id);
632 }
633 else
634 {
635 file.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.FileIdFormat, projectName, pogName, fileName));
636 component.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.ComponentIdFormat, projectName, pogName, fileName));
637 }
638 }
639 }
640 }
641
642 private void HarvestProjectOutputGroupPackage(string projectName, string pogName, string pogFileSource, string filePath, string fileName, string link, Wix.IParentElement parentDir, Dictionary<string, bool> seenList)
643 {
644 string varFormat = VariableFormat;
645 if (this.generateWixVars)
646 {
647 varFormat = WixVariableFormat;
648 }
649
650 if (pogName.Equals("Binaries", StringComparison.OrdinalIgnoreCase))
651 {
652 if (String.Equals(Path.GetExtension(filePath), ".exe", StringComparison.OrdinalIgnoreCase))
653 {
654 Wix.ExePackage exePackage = new Wix.ExePackage();
655 exePackage.SourceFile = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", Path.GetFileName(filePath));
656 if (!seenList.ContainsKey(exePackage.SourceFile))
657 {
658 parentDir.AddChild(exePackage);
659 seenList.Add(exePackage.SourceFile, true);
660 }
661 }
662 else if (String.Equals(Path.GetExtension(filePath), ".msi", StringComparison.OrdinalIgnoreCase))
663 {
664 Wix.MsiPackage msiPackage = new Wix.MsiPackage();
665 msiPackage.SourceFile = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", Path.GetFileName(filePath));
666 if (!seenList.ContainsKey(msiPackage.SourceFile))
667 {
668 parentDir.AddChild(msiPackage);
669 seenList.Add(msiPackage.SourceFile, true);
670 }
671 }
672 }
673 }
674
675 private void HarvestProjectOutputGroupPayloadFile(string baseDir, string projectName, string pogName, string pogFileSource, string filePath, string fileName, string link, Wix.IParentElement parentDir, Wix.Payload file, Dictionary<string, bool> seenList)
676 {
677 string varFormat = VariableFormat;
678 if (this.generateWixVars)
679 {
680 varFormat = WixVariableFormat;
681 }
682
683 if (pogName.Equals("Satellites", StringComparison.OrdinalIgnoreCase))
684 {
685 string locDirectoryName = Path.GetFileName(Path.GetDirectoryName(Path.GetFullPath(filePath)));
686 file.SourceFile = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", locDirectoryName, "\\", Path.GetFileName(filePath));
687
688 if (!seenList.ContainsKey(file.SourceFile))
689 {
690 parentDir.AddChild(file);
691 seenList.Add(file.SourceFile, true);
692 }
693 }
694 else
695 {
696 file.SourceFile = GenerateSourceFilePath(baseDir, projectName, pogFileSource, filePath, link, varFormat);
697
698 if (!seenList.ContainsKey(file.SourceFile))
699 {
700 parentDir.AddChild(file);
701 seenList.Add(file.SourceFile, true);
702 }
703 }
704 }
705
706 /// <summary>
707 /// Helper function to generates a source file path when harvesting files.
708 /// </summary>
709 /// <param name="baseDir"></param>
710 /// <param name="projectName"></param>
711 /// <param name="pogFileSource"></param>
712 /// <param name="filePath"></param>
713 /// <param name="link"></param>
714 /// <param name="varFormat"></param>
715 /// <returns></returns>
716 private static string GenerateSourceFilePath(string baseDir, string projectName, string pogFileSource, string filePath, string link, string varFormat)
717 {
718 string ret;
719
720 if (null == baseDir && !String.IsNullOrEmpty(link))
721 {
722 // This needs to be the absolute path as a link can be located anywhere.
723 ret = filePath;
724 }
725 else if (null == baseDir)
726 {
727 ret = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", Path.GetFileName(filePath));
728 }
729 else if (filePath.StartsWith(baseDir, StringComparison.OrdinalIgnoreCase))
730 {
731 ret = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", filePath.Substring(baseDir.Length));
732 }
733 else
734 {
735 // come up with a relative path to the file
736 Uri sourcePathUri = new Uri(filePath);
737 Uri baseDirUri = new Uri(baseDir);
738 Uri sourceRelativeUri = baseDirUri.MakeRelativeUri(sourcePathUri);
739 string relativePath = sourceRelativeUri.ToString().Replace('/', Path.DirectorySeparatorChar);
740 if (!sourceRelativeUri.UserEscaped)
741 {
742 relativePath = Uri.UnescapeDataString(relativePath);
743 }
744
745 ret = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", relativePath);
746 }
747
748 return ret;
749 }
750
751 /// <summary>
752 /// Gets a Directory element corresponding to a relative subdirectory within the project,
753 /// either by locating a suitable existing Directory or creating a new one.
754 /// </summary>
755 /// <param name="parentDir">The parent element which the subdirectory is relative to.</param>
756 /// <param name="relativeUri">Relative path of the subdirectory.</param>
757 /// <returns>Directory element for the relative path.</returns>
758 private Wix.IParentElement GetSubDirElement(Wix.IParentElement parentDir, Uri relativeUri)
759 {
760 string[] segments = relativeUri.ToString().Split('\\', '/');
761 string firstSubDirName = Uri.UnescapeDataString(segments[0]);
762 DirectoryAttributeAccessor subDir = null;
763
764 if (String.Equals(firstSubDirName, "..", StringComparison.Ordinal))
765 {
766 return parentDir;
767 }
768
769 Type directoryType;
770 Type directoryRefType;
771 if (parentDir is Wix.Directory || parentDir is Wix.DirectoryRef)
772 {
773 directoryType = typeof(Wix.Directory);
774 directoryRefType = typeof(Wix.DirectoryRef);
775 }
776 else
777 {
778 throw new ArgumentException("GetSubDirElement parentDir");
779 }
780
781 // Search for an existing directory element.
782 foreach (Wix.ISchemaElement childElement in parentDir.Children)
783 {
784 if(VSProjectHarvester.AreTypesEquivalent(childElement.GetType(), directoryType))
785 {
786 DirectoryAttributeAccessor childDir = new DirectoryAttributeAccessor(childElement);
787 if (String.Equals(childDir.Name, firstSubDirName, StringComparison.OrdinalIgnoreCase))
788 {
789 subDir = childDir;
790 break;
791 }
792 }
793 }
794
795 if (subDir == null)
796 {
797 string parentId = null;
798 DirectoryAttributeAccessor parentDirectory = null;
799 DirectoryAttributeAccessor parentDirectoryRef = null;
800
801 if (VSProjectHarvester.AreTypesEquivalent(parentDir.GetType(), directoryType))
802 {
803 parentDirectory = new DirectoryAttributeAccessor((Wix.ISchemaElement)parentDir);
804 }
805 else if (VSProjectHarvester.AreTypesEquivalent(parentDir.GetType(), directoryRefType))
806 {
807 parentDirectoryRef = new DirectoryAttributeAccessor((Wix.ISchemaElement)parentDir);
808 }
809
810 if (parentDirectory != null)
811 {
812 parentId = parentDirectory.Id;
813 }
814 else if (parentDirectoryRef != null)
815 {
816 if (this.setUniqueIdentifiers)
817 {
818 //Use the GUID of the project instead of the project name to help keep things stable.
819 parentId = this.directoryRefSeed;
820 }
821 else
822 {
823 parentId = parentDirectoryRef.Id;
824 }
825 }
826
827 Wix.ISchemaElement newDirectory = (Wix.ISchemaElement)directoryType.GetConstructor(new Type[] { }).Invoke(null);
828 subDir = new DirectoryAttributeAccessor(newDirectory);
829
830 if (this.setUniqueIdentifiers)
831 {
832 subDir.Id = this.Core.GenerateIdentifier(DirectoryPrefix, parentId, firstSubDirName);
833 }
834 else
835 {
836 subDir.Id = String.Format(DirectoryIdFormat, parentId, firstSubDirName);
837 }
838
839 subDir.Name = firstSubDirName;
840
841 parentDir.AddChild(subDir.Element);
842 }
843
844 if (segments.Length == 1)
845 {
846 return subDir.ElementAsParent;
847 }
848 else
849 {
850 Uri nextRelativeUri = new Uri(Uri.UnescapeDataString(relativeUri.ToString()).Substring(firstSubDirName.Length + 1), UriKind.Relative);
851 return this.GetSubDirElement(subDir.ElementAsParent, nextRelativeUri);
852 }
853 }
854
855 private MSBuildProject GetMsbuildProject(string projectFile)
856 {
857 XmlDocument document = new XmlDocument();
858 try
859 {
860 document.Load(projectFile);
861 }
862 catch (Exception e)
863 {
864 throw new WixException(HarvesterErrors.CannotLoadProject(projectFile, e.Message));
865 }
866
867 string version = null;
868
869 if (this.UseToolsVersion)
870 {
871 foreach (XmlNode child in document.ChildNodes)
872 {
873 if (String.Equals(child.Name, "Project", StringComparison.Ordinal) && child.Attributes != null)
874 {
875 XmlNode toolsVersionAttribute = child.Attributes["ToolsVersion"];
876 if (toolsVersionAttribute != null)
877 {
878 version = toolsVersionAttribute.Value;
879 this.Core.Messaging.Write(HarvesterVerboses.FoundToolsVersion(version));
880
881 break;
882 }
883 }
884 }
885
886 switch (version)
887 {
888 case "4.0":
889 version = "4.0.0.0";
890 break;
891 case "12.0":
892 version = "12.0.0.0";
893 break;
894 case "14.0":
895 version = "14.0.0.0";
896 break;
897 default:
898 if (String.IsNullOrEmpty(this.MsbuildBinPath))
899 {
900 throw new WixException(HarvesterErrors.MsbuildBinPathRequired(version ?? "(none)"));
901 }
902
903 version = null;
904 break;
905 }
906 }
907
908 var project = this.ConstructMsbuild40Project(version);
909 return project;
910 }
911
912 private Assembly ResolveFromMsbuildBinPath(object sender, ResolveEventArgs args)
913 {
914 var assemblyName = new AssemblyName(args.Name);
915
916 var assemblyPath = Path.Combine(this.MsbuildBinPath, $"{assemblyName.Name}.dll");
917 if (!File.Exists(assemblyPath))
918 {
919 return null;
920 }
921
922 return Assembly.LoadFrom(assemblyPath);
923 }
924
925 private MSBuildProject ConstructMsbuild40Project(string loadVersion)
926 {
927 const string MSBuildEngineAssemblyName = "Microsoft.Build, Version={0}, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
928 const string MSBuildFrameworkAssemblyName = "Microsoft.Build.Framework, Version={0}, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
929 Assembly msbuildAssembly;
930 Assembly msbuildFrameworkAssembly;
931
932 if (loadVersion == null)
933 {
934 this.Core.Messaging.Write(HarvesterVerboses.LoadingProjectWithBinPath(this.MsbuildBinPath));
935 AppDomain.CurrentDomain.AssemblyResolve += this.ResolveFromMsbuildBinPath;
936
937 try
938 {
939 msbuildAssembly = Assembly.Load("Microsoft.Build");
940 }
941 catch (Exception e)
942 {
943 throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message));
944 }
945
946 try
947 {
948 msbuildFrameworkAssembly = Assembly.Load("Microsoft.Build.Framework");
949 }
950 catch (Exception e)
951 {
952 throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message));
953 }
954 }
955 else
956 {
957 this.Core.Messaging.Write(HarvesterVerboses.LoadingProjectWithVersion(loadVersion));
958
959 try
960 {
961 msbuildAssembly = Assembly.Load(String.Format(MSBuildEngineAssemblyName, loadVersion));
962 }
963 catch (Exception e)
964 {
965 throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message));
966 }
967
968 try
969 {
970 msbuildFrameworkAssembly = Assembly.Load(String.Format(MSBuildFrameworkAssemblyName, loadVersion));
971 }
972 catch (Exception e)
973 {
974 throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message));
975 }
976 }
977
978 Type projectType;
979 Type buildItemType;
980
981 Type buildManagerType;
982 Type buildParametersType;
983 Type buildRequestDataFlagsType;
984 Type buildRequestDataType;
985 Type hostServicesType;
986 Type projectCollectionType;
987 Type projectInstanceType;
988
989 Type writeHandlerType;
990 Type colorSetterType;
991 Type colorResetterType;
992 Type loggerVerbosityType;
993 Type consoleLoggerType;
994 Type iLoggerType;
995
996 try
997 {
998 buildItemType = msbuildAssembly.GetType("Microsoft.Build.Execution.ProjectItemInstance", true);
999 projectType = msbuildAssembly.GetType("Microsoft.Build.Evaluation.Project", true);
1000
1001 buildManagerType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildManager", true);
1002 buildParametersType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildParameters", true);
1003 buildRequestDataFlagsType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildRequestDataFlags", true);
1004 buildRequestDataType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildRequestData", true);
1005 hostServicesType = msbuildAssembly.GetType("Microsoft.Build.Execution.HostServices", true);
1006 projectCollectionType = msbuildAssembly.GetType("Microsoft.Build.Evaluation.ProjectCollection", true);
1007 projectInstanceType = msbuildAssembly.GetType("Microsoft.Build.Execution.ProjectInstance", true);
1008
1009 writeHandlerType = msbuildAssembly.GetType("Microsoft.Build.Logging.WriteHandler", true);
1010 colorSetterType = msbuildAssembly.GetType("Microsoft.Build.Logging.ColorSetter", true);
1011 colorResetterType = msbuildAssembly.GetType("Microsoft.Build.Logging.ColorResetter", true);
1012 loggerVerbosityType = msbuildFrameworkAssembly.GetType("Microsoft.Build.Framework.LoggerVerbosity", true);
1013 consoleLoggerType = msbuildAssembly.GetType("Microsoft.Build.Logging.ConsoleLogger", true);
1014 iLoggerType = msbuildFrameworkAssembly.GetType("Microsoft.Build.Framework.ILogger", true);
1015 }
1016 catch (TargetInvocationException tie)
1017 {
1018 throw new WixException(HarvesterErrors.CannotLoadMSBuildEngine(tie.InnerException.Message));
1019 }
1020 catch (Exception e)
1021 {
1022 throw new WixException(HarvesterErrors.CannotLoadMSBuildEngine(e.Message));
1023 }
1024
1025 MSBuild40Types types = new MSBuild40Types();
1026 types.buildManagerType = buildManagerType;
1027 types.buildParametersType = buildParametersType;
1028 types.buildRequestDataFlagsType = buildRequestDataFlagsType;
1029 types.buildRequestDataType = buildRequestDataType;
1030 types.hostServicesType = hostServicesType;
1031 types.projectCollectionType = projectCollectionType;
1032 types.projectInstanceType = projectInstanceType;
1033 types.writeHandlerType = writeHandlerType;
1034 types.colorSetterType = colorSetterType;
1035 types.colorResetterType = colorResetterType;
1036 types.loggerVerbosityType = loggerVerbosityType;
1037 types.consoleLoggerType = consoleLoggerType;
1038 types.iLoggerType = iLoggerType;
1039 return new MSBuild40Project(null, projectType, buildItemType, loadVersion, types, this.Core, this.configuration, this.platform);
1040 }
1041
1042 private static bool AreTypesEquivalent(Type a, Type b)
1043 {
1044 return (a == b) || (a.IsAssignableFrom(b) && b.IsAssignableFrom(a));
1045 }
1046
1047 private abstract class MSBuildProject
1048 {
1049 protected Type projectType;
1050 protected Type buildItemType;
1051 protected object project;
1052 private string loadVersion;
1053
1054 public MSBuildProject(object project, Type projectType, Type buildItemType, string loadVersion)
1055 {
1056 this.project = project;
1057 this.projectType = projectType;
1058 this.buildItemType = buildItemType;
1059 this.loadVersion = loadVersion;
1060 }
1061
1062 public string LoadVersion
1063 {
1064 get { return this.loadVersion; }
1065 }
1066
1067 public abstract bool Build(string projectFileName, string[] targetNames, IDictionary targetOutputs);
1068
1069 public abstract MSBuildProjectItemType GetBuildItem(object buildItem);
1070
1071 public abstract IEnumerable GetEvaluatedItemsByName(string itemName);
1072
1073 public abstract string GetEvaluatedProperty(string propertyName);
1074
1075 public abstract void Load(string projectFileName);
1076 }
1077
1078 private abstract class MSBuildProjectItemType
1079 {
1080 public MSBuildProjectItemType(object buildItem)
1081 {
1082 this.buildItem = buildItem;
1083 }
1084
1085 public abstract override string ToString();
1086
1087 public abstract string GetMetadata(string name);
1088
1089 protected object buildItem;
1090 }
1091
1092
1093 private struct MSBuild40Types
1094 {
1095 public Type buildManagerType;
1096 public Type buildParametersType;
1097 public Type buildRequestDataFlagsType;
1098 public Type buildRequestDataType;
1099 public Type hostServicesType;
1100 public Type projectCollectionType;
1101 public Type projectInstanceType;
1102 public Type writeHandlerType;
1103 public Type colorSetterType;
1104 public Type colorResetterType;
1105 public Type loggerVerbosityType;
1106 public Type consoleLoggerType;
1107 public Type iLoggerType;
1108 }
1109
1110 private class MSBuild40Project : MSBuildProject
1111 {
1112 private MSBuild40Types types;
1113 private object projectCollection;
1114 private object currentProjectInstance;
1115 private object buildManager;
1116 private object buildParameters;
1117 private IHarvesterCore harvesterCore;
1118
1119 public MSBuild40Project(object project, Type projectType, Type buildItemType, string loadVersion, MSBuild40Types types, IHarvesterCore harvesterCore, string configuration, string platform)
1120 : base(project, projectType, buildItemType, loadVersion)
1121 {
1122 this.types = types;
1123 this.harvesterCore = harvesterCore;
1124
1125 this.buildParameters = this.types.buildParametersType.GetConstructor(new Type[] { }).Invoke(null);
1126
1127 try
1128 {
1129 var loggers = this.CreateLoggers();
1130
1131 // this.buildParameters.Loggers = loggers;
1132 this.types.buildParametersType.GetProperty("Loggers").SetValue(this.buildParameters, loggers, null);
1133 }
1134 catch (TargetInvocationException tie)
1135 {
1136 if (this.harvesterCore != null)
1137 {
1138 this.harvesterCore.Messaging.Write(HarvesterWarnings.NoLogger(tie.InnerException.Message));
1139 }
1140 }
1141 catch (Exception e)
1142 {
1143 if (this.harvesterCore != null)
1144 {
1145 this.harvesterCore.Messaging.Write(HarvesterWarnings.NoLogger(e.Message));
1146 }
1147 }
1148
1149 this.buildManager = this.types.buildManagerType.GetConstructor(new Type[] { }).Invoke(null);
1150
1151 if (configuration != null || platform != null)
1152 {
1153 Dictionary<string, string> globalVariables = new Dictionary<string, string>();
1154 if (configuration != null)
1155 {
1156 globalVariables.Add("Configuration", configuration);
1157 }
1158
1159 if (platform != null)
1160 {
1161 globalVariables.Add("Platform", platform);
1162 }
1163
1164 this.projectCollection = this.types.projectCollectionType.GetConstructor(new Type[] { typeof(IDictionary<string, string>) }).Invoke(new object[] { globalVariables });
1165 }
1166 else
1167 {
1168 this.projectCollection = this.types.projectCollectionType.GetConstructor(new Type[] {}).Invoke(null);
1169 }
1170 }
1171
1172 private object CreateLoggers()
1173 {
1174 var logger = new HarvestLogger(this.harvesterCore.Messaging);
1175 var loggerVerbosity = Enum.Parse(this.types.loggerVerbosityType, "Minimal");
1176 var writeHandler = Delegate.CreateDelegate(this.types.writeHandlerType, logger, nameof(logger.LogMessage));
1177 var colorSetter = Delegate.CreateDelegate(this.types.colorSetterType, logger, nameof(logger.SetColor));
1178 var colorResetter = Delegate.CreateDelegate(this.types.colorResetterType, logger, nameof(logger.ResetColor));
1179
1180 var consoleLoggerCtor = this.types.consoleLoggerType.GetConstructor(new Type[] {
1181 this.types.loggerVerbosityType,
1182 this.types.writeHandlerType,
1183 this.types.colorSetterType,
1184 this.types.colorResetterType,
1185 });
1186 var consoleLogger = consoleLoggerCtor.Invoke(new object[] { loggerVerbosity, writeHandler, colorSetter, colorResetter });
1187
1188 var loggers = Array.CreateInstance(this.types.iLoggerType, 1);
1189 loggers.SetValue(consoleLogger, 0);
1190
1191 return loggers;
1192 }
1193
1194 public override bool Build(string projectFileName, string[] targetNames, IDictionary targetOutputs)
1195 {
1196 try
1197 {
1198 // this.buildManager.BeginBuild(this.buildParameters);
1199 this.types.buildManagerType.GetMethod("BeginBuild", new Type[] { this.types.buildParametersType }).Invoke(this.buildManager, new object[] { this.buildParameters });
1200
1201 // buildRequestData = new BuildRequestData(this.currentProjectInstance, targetNames, null, BuildRequestData.BuildRequestDataFlags.ReplaceExistingProjectInstance);
1202 ConstructorInfo buildRequestDataCtor = this.types.buildRequestDataType.GetConstructor(
1203 new Type[]
1204 {
1205 this.types.projectInstanceType, typeof(string[]), this.types.hostServicesType, this.types.buildRequestDataFlagsType
1206 });
1207 object buildRequestDataFlags = this.types.buildRequestDataFlagsType.GetField("ReplaceExistingProjectInstance").GetRawConstantValue();
1208 object buildRequestData = buildRequestDataCtor.Invoke(new object[] { this.currentProjectInstance, targetNames, null, buildRequestDataFlags });
1209
1210 // BuildSubmission submission = this.buildManager.PendBuildRequest(buildRequestData);
1211 object submission = this.types.buildManagerType.GetMethod("PendBuildRequest", new Type[] { this.types.buildRequestDataType })
1212 .Invoke(this.buildManager, new object[] { buildRequestData });
1213
1214 // BuildResult buildResult = submission.Execute();
1215 object buildResult = submission.GetType().GetMethod("Execute", new Type[] { }).Invoke(submission, null);
1216
1217 // bool buildSucceeded = buildResult.OverallResult == BuildResult.Success;
1218 object overallResult = buildResult.GetType().GetProperty("OverallResult").GetValue(buildResult, null);
1219 bool buildSucceeded = String.Equals(overallResult.ToString(), "Success", StringComparison.Ordinal);
1220
1221 // this.buildManager.EndBuild();
1222 this.types.buildManagerType.GetMethod("EndBuild", new Type[] { }).Invoke(this.buildManager, null);
1223
1224 // fill in empty lists for each target so that heat will look at the item group later
1225 foreach (string target in targetNames)
1226 {
1227 targetOutputs.Add(target, new List<object>());
1228 }
1229
1230 return buildSucceeded;
1231 }
1232 catch (TargetInvocationException tie)
1233 {
1234 throw new WixException(HarvesterErrors.CannotBuildProject(projectFileName, tie.InnerException.Message));
1235 }
1236 catch (Exception e)
1237 {
1238 throw new WixException(HarvesterErrors.CannotBuildProject(projectFileName, e.Message));
1239 }
1240 }
1241
1242 public override MSBuildProjectItemType GetBuildItem(object buildItem)
1243 {
1244 return new MSBuild40ProjectItemType(buildItem);
1245 }
1246
1247 public override IEnumerable GetEvaluatedItemsByName(string itemName)
1248 {
1249 MethodInfo getEvaluatedItem = this.types.projectInstanceType.GetMethod("GetItems", new Type[] { typeof(string) });
1250 return (IEnumerable)getEvaluatedItem.Invoke(this.currentProjectInstance, new object[] { itemName });
1251 }
1252
1253 public override string GetEvaluatedProperty(string propertyName)
1254 {
1255 MethodInfo getProperty = this.types.projectInstanceType.GetMethod("GetPropertyValue", new Type[] { typeof(string) });
1256 return (string)getProperty.Invoke(this.currentProjectInstance, new object[] { propertyName });
1257 }
1258
1259 public override void Load(string projectFileName)
1260 {
1261 try
1262 {
1263 //this.project = this.projectCollection.LoadProject(projectFileName);
1264 this.project = this.types.projectCollectionType.GetMethod("LoadProject", new Type[] { typeof(string) }).Invoke(this.projectCollection, new object[] { projectFileName });
1265
1266 // this.currentProjectInstance = this.project.CreateProjectInstance();
1267 MethodInfo createProjectInstanceMethod = this.projectType.GetMethod("CreateProjectInstance", new Type[] { });
1268 this.currentProjectInstance = createProjectInstanceMethod.Invoke(this.project, null);
1269 }
1270 catch (TargetInvocationException tie)
1271 {
1272 throw new WixException(HarvesterErrors.CannotLoadProject(projectFileName, tie.InnerException.Message));
1273 }
1274 catch (Exception e)
1275 {
1276 throw new WixException(HarvesterErrors.CannotLoadProject(projectFileName, e.Message));
1277 }
1278 }
1279 }
1280
1281 private class MSBuild40ProjectItemType : MSBuildProjectItemType
1282 {
1283 public MSBuild40ProjectItemType(object buildItem)
1284 : base(buildItem)
1285 {
1286 }
1287
1288 public override string ToString()
1289 {
1290 PropertyInfo includeProperty = this.buildItem.GetType().GetProperty("EvaluatedInclude");
1291 return (string)includeProperty.GetValue(this.buildItem, null);
1292 }
1293
1294 public override string GetMetadata(string name)
1295 {
1296 MethodInfo getMetadataMethod = this.buildItem.GetType().GetMethod("GetMetadataValue");
1297 if (null != getMetadataMethod)
1298 {
1299 return (string)getMetadataMethod.Invoke(this.buildItem, new object[] { name });
1300 }
1301 return string.Empty;
1302 }
1303 }
1304
1305 /// <summary>
1306 /// Used internally in the VSProjectHarvester class to encapsulate
1307 /// the settings for a particular MSBuild "project output group".
1308 /// </summary>
1309 private struct ProjectOutputGroup
1310 {
1311 public readonly string Name;
1312 public readonly string BuildOutputGroup;
1313 public readonly string FileSource;
1314
1315 /// <summary>
1316 /// Creates a new project output group.
1317 /// </summary>
1318 /// <param name="name">Friendly name used by heat.</param>
1319 /// <param name="buildOutputGroup">MSBuild's name of the project output group.</param>
1320 /// <param name="fileSource">VS directory token containing the files of the POG.</param>
1321 public ProjectOutputGroup(string name, string buildOutputGroup, string fileSource)
1322 {
1323 this.Name = name;
1324 this.BuildOutputGroup = buildOutputGroup;
1325 this.FileSource = fileSource;
1326 }
1327 }
1328
1329 /// <summary>
1330 /// Internal class for getting and setting common attrbiutes on
1331 /// directory elements.
1332 /// </summary>
1333 internal class DirectoryAttributeAccessor
1334 {
1335 public Wix.ISchemaElement directoryElement;
1336
1337 public DirectoryAttributeAccessor(Wix.ISchemaElement directoryElement)
1338 {
1339 this.directoryElement = directoryElement;
1340 }
1341
1342 /// <summary>
1343 /// Gets the element as a ISchemaElement.
1344 /// </summary>
1345 public Wix.ISchemaElement Element
1346 {
1347 get { return this.directoryElement; }
1348 }
1349
1350 /// <summary>
1351 /// Gets the element as a IParentElement.
1352 /// </summary>
1353 public Wix.IParentElement ElementAsParent
1354 {
1355 get { return (Wix.IParentElement)this.directoryElement; }
1356 }
1357
1358 /// <summary>
1359 /// Gets or sets the Id attrbiute.
1360 /// </summary>
1361 public string Id
1362 {
1363 get
1364 {
1365 if (this.directoryElement is Wix.Directory wixDirectory)
1366 {
1367 return wixDirectory.Id;
1368 }
1369 else if (this.directoryElement is Wix.DirectoryRef wixDirectoryRef)
1370 {
1371 return wixDirectoryRef.Id;
1372 }
1373 else
1374 {
1375 throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Id"));
1376 }
1377 }
1378 set
1379 {
1380 if (this.directoryElement is Wix.Directory wixDirectory)
1381 {
1382 wixDirectory.Id = value;
1383 }
1384 else if (this.directoryElement is Wix.DirectoryRef wixDirectoryRef)
1385 {
1386 wixDirectoryRef.Id = value;
1387 }
1388 else
1389 {
1390 throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Id"));
1391 }
1392 }
1393 }
1394
1395 /// <summary>
1396 /// Gets or sets the Name attribute.
1397 /// </summary>
1398 public string Name
1399 {
1400 get
1401 {
1402 if (this.directoryElement is Wix.Directory wixDirectory)
1403 {
1404 return wixDirectory.Name;
1405 }
1406 else
1407 {
1408 throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Name"));
1409 }
1410 }
1411 set
1412 {
1413 if (this.directoryElement is Wix.Directory wixDirectory)
1414 {
1415 wixDirectory.Name = value;
1416 }
1417 else
1418 {
1419 throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Name"));
1420 }
1421 }
1422 }
1423 }
1424
1425 internal class HarvestLogger
1426 {
1427 public HarvestLogger(IMessaging messaging)
1428 {
1429 this.Color = ConsoleColor.Black;
1430 this.Messaging = messaging;
1431 }
1432
1433 private ConsoleColor Color { get; set; }
1434 private IMessaging Messaging { get; }
1435
1436 public void LogMessage(string message)
1437 {
1438 if (this.Color == ConsoleColor.Red)
1439 {
1440 this.Messaging.Write(HarvesterErrors.BuildErrorDuringHarvesting(message));
1441 }
1442 }
1443
1444 public void SetColor(ConsoleColor color)
1445 {
1446 this.Color = color;
1447 }
1448
1449 public void ResetColor()
1450 {
1451 this.Color = ConsoleColor.Black;
1452 }
1453 }
1454 }
1455}