1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 //-----------------------------------------------------------------------
4 // </copyright>
5 // <summary>An object containing properties of a toolset.</summary>
6 //-----------------------------------------------------------------------
7 
8 using System;
9 using System.Collections.Generic;
10 using System.Diagnostics;
11 using System.Diagnostics.CodeAnalysis;
12 using System.IO;
13 using System.Linq;
14 using System.Xml;
15 
16 using Microsoft.Build.BackEnd;
17 using Microsoft.Build.Collections;
18 using Microsoft.Build.Construction;
19 using Microsoft.Build.Execution;
20 using Microsoft.Build.Framework;
21 using Microsoft.Build.Internal;
22 using Microsoft.Build.Shared;
23 using Microsoft.Win32;
24 using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService;
25 using ObjectModel = System.Collections.ObjectModel;
26 using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames;
27 
28 namespace Microsoft.Build.Evaluation
29 {
30     /// <summary>
31     /// Delegate for loading an Xml file, for unit testing.
32     /// </summary>
33     /// <param name="path">The path to load.</param>
34     /// <returns>An Xml document.</returns>
LoadXmlFromPath(string path)35     internal delegate XmlDocumentWithLocation LoadXmlFromPath(string path);
36 
37     /// <summary>
38     /// Aggregation of a toolset version (eg. "2.0"), tools path, and optional set of associated properties.
39     /// Toolset is immutable.
40     /// </summary>
41     /// <remarks>
42     /// UNDONE: Review immutability. If this is not immutable, add a mechanism to notify the project collection/s owning it to increment their toolsetVersion.
43     /// </remarks>
44     [DebuggerDisplay("ToolsVersion={ToolsVersion} ToolsPath={ToolsPath} #Properties={_properties.Count}")]
45     public class Toolset : INodePacketTranslatable
46     {
47         /// <summary>
48         /// these files list all default tasks and task assemblies that do not need to be explicitly declared by projects
49         /// </summary>
50         private const string DefaultTasksFilePattern = "*.tasks";
51 
52         /// <summary>
53         /// these files list all Override tasks and task assemblies that do not need to be explicitly declared by projects
54         /// </summary>
55         private const string OverrideTasksFilePattern = "*.overridetasks";
56 
57         /// <summary>
58         /// Regkey that we check to see whether Dev10 is installed.  This should exist if any SKU of Dev10 is installed,
59         /// but is not removed even when the last version of Dev10 is uninstalled, due to 10.0\bsln sticking around.
60         /// </summary>
61         private const string Dev10OverallInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0";
62 
63         /// <summary>
64         /// Regkey that we check to see whether Dev10 Ultimate is installed.  This will exist if it is installed, and be
65         /// properly removed after it has been uninstalled.
66         /// </summary>
67         private const string Dev10UltimateInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0\vstscore";
68 
69         /// <summary>
70         /// Regkey that we check to see whether Dev10 Premium is installed.  This will exist if it is installed, and be
71         /// properly removed after it has been uninstalled.
72         /// </summary>
73         private const string Dev10PremiumInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0\vstdcore";
74 
75         /// <summary>
76         /// Regkey that we check to see whether Dev10 Professional is installed.  This will exist if it is installed, and be
77         /// properly removed after it has been uninstalled.
78         /// </summary>
79         private const string Dev10ProfessionalInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0\procore";
80 
81         /// <summary>
82         /// Regkey that we check to see whether C# Express 2010 is installed.  This will exist if it is installed, and be
83         /// properly removed after it has been uninstalled.
84         /// </summary>
85         private const string Dev10VCSExpressInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vcs\Servicing\10.0\xcor";
86 
87         /// <summary>
88         /// Regkey that we check to see whether VB Express 2010 is installed.  This will exist if it is installed, and be
89         /// properly removed after it has been uninstalled.
90         /// </summary>
91         private const string Dev10VBExpressInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vb\Servicing\10.0\xcor";
92 
93         /// <summary>
94         /// Regkey that we check to see whether VC Express 2010 is installed.  This will exist if it is installed, and be
95         /// properly removed after it has been uninstalled.
96         /// </summary>
97         private const string Dev10VCExpressInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vc\Servicing\10.0\xcor";
98 
99         /// <summary>
100         /// Regkey that we check to see whether VWD Express 2010 is installed.  This will exist if it is installed, and be
101         /// properly removed after it has been uninstalled.
102         /// </summary>
103         private const string Dev10VWDExpressInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vns\Servicing\10.0\xcor";
104 
105         /// <summary>
106         /// Regkey that we check to see whether LightSwitch 2010 is installed.  This will exist if it is installed, and be
107         /// properly removed after it has been uninstalled.
108         /// </summary>
109         private const string Dev10LightSwitchInstallKeyRegistryPath = @"Software\Microsoft\DevDiv\vs\Servicing\10.0\vslscore";
110 
111 #if FEATURE_WIN32_REGISTRY
112         /// <summary>
113         /// Null if it hasn't been figured out yet; true if (some variation of) Visual Studio 2010 is installed on
114         /// the current machine, false otherwise.
115         /// </summary>
116         private static bool? s_dev10IsInstalled = null;
117 #endif
118 
119         /// <summary>
120         /// Name of the tools version
121         /// </summary>
122         private string _toolsVersion;
123 
124         /// <summary>
125         /// The MSBuildBinPath (and ToolsPath) for this tools version
126         /// </summary>
127         private string _toolsPath;
128 
129         /// <summary>
130         /// The properties defined by the toolset.
131         /// </summary>
132         private PropertyDictionary<ProjectPropertyInstance> _properties;
133 
134         /// <summary>
135         /// Path to look for msbuild override task files.
136         /// </summary>
137         private string _overrideTasksPath;
138 
139         /// <summary>
140         /// ToolsVersion to use as the default ToolsVersion for this version of MSBuild
141         /// </summary>
142         private string _defaultOverrideToolsVersion;
143 
144         /// <summary>
145         /// The environment properties
146         /// </summary>
147         private PropertyDictionary<ProjectPropertyInstance> _environmentProperties;
148 
149         /// <summary>
150         /// The build-global properties
151         /// </summary>
152         private PropertyDictionary<ProjectPropertyInstance> _globalProperties;
153 
154         /// <summary>
155         /// indicates if the default tasks file has already been scanned
156         /// </summary>
157         private bool _defaultTasksRegistrationAttempted;
158 
159         /// <summary>
160         /// indicates if the override tasks file has already been scanned
161         /// </summary>
162         private bool _overrideTasksRegistrationAttempted;
163 
164         /// <summary>
165         /// holds all the default tasks we know about and the assemblies they exist in
166         /// </summary>
167         private TaskRegistry _defaultTaskRegistry;
168 
169         /// <summary>
170         /// holds all the override tasks we know about and the assemblies they exist in
171         /// </summary>
172         private TaskRegistry _overrideTaskRegistry;
173 
174         /// <summary>
175         /// Delegate to retrieving files.  For unit testing only.
176         /// </summary>
177         private DirectoryGetFiles _getFiles;
178 
179         /// <summary>
180         /// Delegate to check to see if a directory exists
181         /// </summary>
182         private DirectoryExists _directoryExists = null;
183 
184         /// <summary>
185         /// Delegate for loading Xml.  For unit testing only.
186         /// </summary>
187         private LoadXmlFromPath _loadXmlFromPath;
188 
189         /// <summary>
190         /// Expander to expand the properties and items in the using tasks files
191         /// </summary>
192         private Expander<ProjectPropertyInstance, ProjectItemInstance> _expander;
193 
194         /// <summary>
195         /// Bag of properties for the expander to expand the properties and items in the using tasks files
196         /// </summary>
197         private PropertyDictionary<ProjectPropertyInstance> _propertyBag;
198 
199         /// <summary>
200         /// SubToolsets that map to this toolset.
201         /// </summary>
202         private Dictionary<string, SubToolset> _subToolsets;
203 
204         /// <summary>
205         /// If no sub-toolset is specified, this is the default sub-toolset version.  Null == no default
206         /// sub-toolset, just use the base toolset.
207         /// </summary>
208         private string _defaultSubToolsetVersion;
209 
210         /// <summary>
211         /// Map of project import properties to their list of fall-back search paths
212         /// </summary>
213         private Dictionary<string, ProjectImportPathMatch> _propertySearchPathsTable;
214 
215         /// <summary>
216         /// Constructor taking only tools version and a matching tools path
217         /// </summary>
218         /// <param name="toolsVersion">Name of the toolset</param>
219         /// <param name="toolsPath">Path to this toolset's tasks and targets</param>
220         /// <param name="projectCollection">The project collection from which to obtain the properties.</param>
221         /// <param name="msbuildOverrideTasksPath">The path to search for msbuild overridetasks files.</param>
Toolset(string toolsVersion, string toolsPath, ProjectCollection projectCollection, string msbuildOverrideTasksPath)222         public Toolset(string toolsVersion, string toolsPath, ProjectCollection projectCollection, string msbuildOverrideTasksPath)
223             : this(toolsVersion, toolsPath, null, projectCollection, msbuildOverrideTasksPath)
224         {
225         }
226 
227         /// <summary>
228         /// Constructor that also associates a set of properties with the tools version
229         /// </summary>
230         /// <param name="toolsVersion">Name of the toolset</param>
231         /// <param name="toolsPath">Path to this toolset's tasks and targets</param>
232         /// <param name="buildProperties">
233         /// Properties that should be associated with the Toolset.
234         /// May be null, in which case an empty property group will be used.
235         /// </param>
236         /// <param name="projectCollection">The project collection that this toolset should inherit from</param>
237         /// <param name="msbuildOverrideTasksPath">The override tasks path.</param>
Toolset(string toolsVersion, string toolsPath, IDictionary<string, string> buildProperties, ProjectCollection projectCollection, string msbuildOverrideTasksPath)238         public Toolset(string toolsVersion, string toolsPath, IDictionary<string, string> buildProperties, ProjectCollection projectCollection, string msbuildOverrideTasksPath)
239             : this(toolsVersion, toolsPath, buildProperties, projectCollection, null, msbuildOverrideTasksPath)
240         {
241         }
242 
243         /// <summary>
244         /// Constructor that also associates a set of properties with the tools version
245         /// </summary>
246         /// <param name="toolsVersion">Name of the toolset</param>
247         /// <param name="toolsPath">Path to this toolset's tasks and targets</param>
248         /// <param name="buildProperties">
249         /// Properties that should be associated with the Toolset.
250         /// May be null, in which case an empty property group will be used.
251         /// </param>
252         /// <param name="projectCollection">The project collection that this toolset should inherit from</param>
253         /// <param name="subToolsets">The set of sub-toolsets to add to this toolset</param>
254         /// <param name="msbuildOverrideTasksPath">The override tasks path.</param>
Toolset(string toolsVersion, string toolsPath, IDictionary<string, string> buildProperties, ProjectCollection projectCollection, IDictionary<string, SubToolset> subToolsets, string msbuildOverrideTasksPath)255         public Toolset(string toolsVersion, string toolsPath, IDictionary<string, string> buildProperties, ProjectCollection projectCollection, IDictionary<string, SubToolset> subToolsets, string msbuildOverrideTasksPath)
256             : this(toolsVersion, toolsPath, null, projectCollection.EnvironmentProperties, projectCollection.GlobalPropertiesCollection, subToolsets, msbuildOverrideTasksPath, defaultOverrideToolsVersion: null)
257         {
258             _properties = new PropertyDictionary<ProjectPropertyInstance>();
259             if (null != buildProperties)
260             {
261                 foreach (KeyValuePair<string, string> keyValuePair in buildProperties)
262                 {
263                     _properties.Set(ProjectPropertyInstance.Create(keyValuePair.Key, keyValuePair.Value, true));
264                 }
265             }
266         }
267 
268         /// <summary>
269         /// Constructor taking only tools version and a matching tools path
270         /// </summary>
271         /// <param name="toolsVersion">Name of the toolset</param>
272         /// <param name="toolsPath">Path to this toolset's tasks and targets</param>
273         /// <param name="environmentProperties">A <see cref="PropertyDictionary{ProjectPropertyInstance}"/> containing the environment properties.</param>
274         /// <param name="globalProperties">A <see cref="PropertyDictionary{ProjectPropertyInstance}"/> containing the global properties.</param>
275         /// <param name="msbuildOverrideTasksPath">The override tasks path.</param>
276         /// <param name="defaultOverrideToolsVersion">ToolsVersion to use as the default ToolsVersion for this version of MSBuild.</param>
Toolset(string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties, string msbuildOverrideTasksPath, string defaultOverrideToolsVersion)277         internal Toolset(string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties, string msbuildOverrideTasksPath, string defaultOverrideToolsVersion)
278         {
279             ErrorUtilities.VerifyThrowArgumentLength(toolsVersion, "toolsVersion");
280             ErrorUtilities.VerifyThrowArgumentLength(toolsPath, "toolsPath");
281             ErrorUtilities.VerifyThrowArgumentNull(environmentProperties, "environmentProperties");
282             ErrorUtilities.VerifyThrowArgumentNull(globalProperties, "globalProperties");
283 
284             _toolsVersion = toolsVersion;
285             this.ToolsPath = toolsPath;
286             _globalProperties = globalProperties;
287             _environmentProperties = environmentProperties;
288             _overrideTasksPath = msbuildOverrideTasksPath;
289             _defaultOverrideToolsVersion = defaultOverrideToolsVersion;
290 
291         }
292 
293         /// <summary>
294         /// Constructor that also associates a set of properties with the tools version
295         /// </summary>
296         /// <param name="toolsVersion">Name of the toolset</param>
297         /// <param name="toolsPath">Path to this toolset's tasks and targets</param>
298         /// <param name="buildProperties">
299         /// Properties that should be associated with the Toolset.
300         /// May be null, in which case an empty property group will be used.
301         /// </param>
302         /// <param name="environmentProperties">A <see cref="PropertyDictionary{ProjectPropertyInstance}"/> containing the environment properties.</param>
303         /// <param name="globalProperties">A <see cref="PropertyDictionary{ProjectPropertyInstance}"/> containing the global properties.</param>
304         /// <param name="subToolsets">A list of <see cref="SubToolset"/> to use.</param>
305         /// <param name="msbuildOverrideTasksPath">The override tasks path.</param>
306         /// <param name="defaultOverrideToolsVersion">ToolsVersion to use as the default ToolsVersion for this version of MSBuild.</param>
307         /// <param name="importSearchPathsTable">Map of parameter name to property search paths for use during Import.</param>
Toolset( string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> buildProperties, PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties, IDictionary<string, SubToolset> subToolsets, string msbuildOverrideTasksPath, string defaultOverrideToolsVersion, Dictionary<string, ProjectImportPathMatch> importSearchPathsTable = null)308         internal Toolset(
309             string toolsVersion,
310             string toolsPath,
311             PropertyDictionary<ProjectPropertyInstance> buildProperties,
312             PropertyDictionary<ProjectPropertyInstance> environmentProperties,
313             PropertyDictionary<ProjectPropertyInstance> globalProperties,
314             IDictionary<string, SubToolset> subToolsets,
315             string msbuildOverrideTasksPath,
316             string defaultOverrideToolsVersion,
317             Dictionary<string, ProjectImportPathMatch> importSearchPathsTable = null)
318             : this(toolsVersion, toolsPath, environmentProperties, globalProperties, msbuildOverrideTasksPath, defaultOverrideToolsVersion)
319         {
320             if (_properties == null)
321             {
322                 _properties = buildProperties != null
323                     ? new PropertyDictionary<ProjectPropertyInstance>(buildProperties)
324                     : new PropertyDictionary<ProjectPropertyInstance>();
325             }
326 
327             if (subToolsets != null)
328             {
329                 Dictionary<string, SubToolset> subToolsetsAsDictionary = subToolsets as Dictionary<string, SubToolset>;
330                 _subToolsets = subToolsetsAsDictionary ?? new Dictionary<string, SubToolset>(subToolsets);
331             }
332 
333             if (importSearchPathsTable != null)
334             {
335                 _propertySearchPathsTable = importSearchPathsTable;
336             }
337         }
338 
339         /// <summary>
340         /// Additional constructor to make unit testing the TaskRegistry support easier
341         /// </summary>
342         /// <remarks>
343         /// Internal for unit test purposes only.
344         /// </remarks>
345         /// <param name="toolsVersion">Name of the toolset</param>
346         /// <param name="toolsPath">Path to this toolset's tasks and targets</param>
347         /// <param name="buildProperties">
348         /// Properties that should be associated with the Toolset.
349         /// May be null, in which case an empty property group will be used.
350         /// </param>
351         /// <param name="projectCollection">The project collection.</param>
352         /// <param name="getFiles">A delegate to intercept GetFiles calls.  For unit testing.</param>
353         /// <param name="loadXmlFromPath">A delegate to intercept Xml load calls.  For unit testing.</param>
354         /// <param name="msbuildOverrideTasksPath">The override tasks path.</param>
355         /// <param name="directoryExists"></param>
Toolset(string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> buildProperties, ProjectCollection projectCollection, DirectoryGetFiles getFiles, LoadXmlFromPath loadXmlFromPath, string msbuildOverrideTasksPath, DirectoryExists directoryExists)356         internal Toolset(string toolsVersion, string toolsPath, PropertyDictionary<ProjectPropertyInstance> buildProperties, ProjectCollection projectCollection, DirectoryGetFiles getFiles, LoadXmlFromPath loadXmlFromPath, string msbuildOverrideTasksPath, DirectoryExists directoryExists)
357             : this(toolsVersion, toolsPath, buildProperties, projectCollection.EnvironmentProperties, projectCollection.GlobalPropertiesCollection, null, msbuildOverrideTasksPath, null)
358         {
359             ErrorUtilities.VerifyThrowInternalNull(getFiles, "getFiles");
360             ErrorUtilities.VerifyThrowInternalNull(loadXmlFromPath, "loadXmlFromPath");
361 
362             _directoryExists = directoryExists;
363             _getFiles = getFiles;
364             _loadXmlFromPath = loadXmlFromPath;
365         }
366 
367         /// <summary>
368         /// Private constructor for serialization.
369         /// </summary>
Toolset(INodePacketTranslator translator)370         private Toolset(INodePacketTranslator translator)
371         {
372             ((INodePacketTranslatable)this).Translate(translator);
373         }
374 
375         /// <summary>
376         /// Returns a ProjectImportPathMatch struct for the first property found in the expression for which
377         /// project import search paths is enabled.
378         /// <param name="expression">Expression to search for properties in (first level only, not recursive)</param>
379         /// <returns>List of search paths or ProjectImportPathMatch.None if empty</returns>
380         /// </summary>
GetProjectImportSearchPaths(string expression)381         internal ProjectImportPathMatch GetProjectImportSearchPaths(string expression)
382         {
383             if (string.IsNullOrEmpty(expression) || ImportPropertySearchPathsTable == null)
384             {
385                 return ProjectImportPathMatch.None;
386             }
387 
388             foreach (var searchPath in _propertySearchPathsTable.Values)
389             {
390                 if (expression.IndexOf(searchPath.MsBuildPropertyFormat, StringComparison.OrdinalIgnoreCase) >= 0)
391                 {
392                     return searchPath;
393                 }
394             }
395 
396             return ProjectImportPathMatch.None;
397         }
398 
399         /// <summary>
400         /// Name of this toolset
401         /// </summary>
402         public string ToolsVersion => _toolsVersion;
403 
404         /// <summary>
405         /// Path to this toolset's tasks and targets. Corresponds to $(MSBuildToolsPath) in a project or targets file.
406         /// </summary>
407         public string ToolsPath
408         {
409             get
410             {
411                 return _toolsPath;
412             }
413 
414             private set
415             {
416                 // Strip the trailing backslash if it exists.  This way, when somebody
417                 // concatenates does something like "$(MSBuildToolsPath)\CSharp.targets",
418                 // they don't end up with a double-backslash in the middle.  (It doesn't
419                 // technically hurt anything, but it doesn't look nice.)
420                 string toolsPathToUse = value;
421 
422                 if (FileUtilities.EndsWithSlash(toolsPathToUse))
423                 {
424                     string rootPath = Path.GetPathRoot(Path.GetFullPath(toolsPathToUse));
425 
426                     // Only if $(MSBuildBinPath) is *NOT* the root of a drive should we strip trailing slashes
427                     if (!String.Equals(rootPath, toolsPathToUse, StringComparison.OrdinalIgnoreCase))
428                     {
429                         // Trim off one trailing slash
430                         toolsPathToUse = toolsPathToUse.Substring(0, toolsPathToUse.Length - 1);
431                     }
432                 }
433 
434                 _toolsPath = toolsPathToUse;
435             }
436         }
437 
438         /// <summary>
439         /// Properties associated with the toolset
440         /// </summary>
441         public IDictionary<string, ProjectPropertyInstance> Properties
442         {
443             get
444             {
445                 if (_properties == null)
446                 {
447                     return ReadOnlyEmptyDictionary<string, ProjectPropertyInstance>.Instance;
448                 }
449 
450                 return new ObjectModel.ReadOnlyDictionary<string, ProjectPropertyInstance>(_properties);
451             }
452         }
453 
454         /// <summary>
455         /// The set of sub-toolsets associated with this toolset.
456         /// </summary>
457         public IDictionary<string, SubToolset> SubToolsets
458         {
459             get
460             {
461                 if (_subToolsets == null)
462                 {
463                     return ReadOnlyEmptyDictionary<string, SubToolset>.Instance;
464                 }
465 
466                 return new ObjectModel.ReadOnlyDictionary<string, SubToolset>(_subToolsets);
467             }
468         }
469 
470         /// <summary>
471         /// Returns the default sub-toolset version for this sub-toolset.  Heuristic used is:
472         /// 1) If Visual Studio 2010 is installed and our ToolsVersion is "4.0", use the base toolset, and return
473         ///    a sub-toolset version of "10.0", to be set as a publicly visible property so that e.g. targets can
474         ///    consume it.  This is to handle the fact that Visual Studio 2010 did not have any concept of sub-toolsets.
475         /// 2) Otherwise, use the highest-versioned sub-toolset found.  Sub-toolsets with numbered versions will
476         ///    be ordered numerically; any additional sub-toolsets will be prepended to the beginning of the list in
477         ///    the order found. We use the highest-versioned sub-toolset because, in the absence of any other information,
478         ///    we assume that higher-versioned tools will be more likely to be able to generate something more correct.
479         ///
480         /// Will return null if there is no sub-toolset available (and Dev10 is not installed).
481         /// </summary>
482         public string DefaultSubToolsetVersion
483         {
484             get
485             {
486                 if (_defaultSubToolsetVersion == null)
487                 {
488                     // 1) Workaround for ToolsVersion 4.0 + VS 2010
489                     if (String.Equals(ToolsVersion, "4.0", StringComparison.OrdinalIgnoreCase) && Dev10IsInstalled)
490                     {
491                         return Constants.Dev10SubToolsetValue;
492                     }
493 
494                     // 2) Otherwise, just pick the highest available.
495                     SortedDictionary<Version, string> subToolsetsWithVersion = new SortedDictionary<Version, string>();
496                     List<string> additionalSubToolsetNames = new List<string>();
497 
498                     foreach (string subToolsetName in SubToolsets.Keys)
499                     {
500                         Version subToolsetVersion = VersionUtilities.ConvertToVersion(subToolsetName);
501 
502                         if (subToolsetVersion != null)
503                         {
504                             subToolsetsWithVersion.Add(subToolsetVersion, subToolsetName);
505                         }
506                         else
507                         {
508                             // if it doesn't parse to an actual version number, shrug and just add it to the end.
509                             additionalSubToolsetNames.Add(subToolsetName);
510                         }
511                     }
512 
513                     List<string> orderedSubToolsetList = new List<string>(additionalSubToolsetNames);
514                     orderedSubToolsetList.AddRange(subToolsetsWithVersion.Values);
515 
516                     if (orderedSubToolsetList.Count > 0)
517                     {
518                         _defaultSubToolsetVersion = orderedSubToolsetList[orderedSubToolsetList.Count - 1];
519                     }
520                 }
521 
522                 return _defaultSubToolsetVersion;
523             }
524         }
525 
526         /// <summary>
527         /// Null if it hasn't been figured out yet; true if (some variation of) Visual Studio 2010 is installed on
528         /// the current machine, false otherwise.
529         /// </summary>
530         /// <comments>
531         /// Internal so that unit tests can use it too.
532         /// </comments>
533         internal static bool Dev10IsInstalled
534         {
535             get
536             {
537 #if FEATURE_WIN32_REGISTRY
538                 if (!NativeMethodsShared.IsWindows)
539                 {
540                     return false;
541                 }
542 
543                 if (s_dev10IsInstalled == null)
544                 {
545                     try
546                     {
547                         // Figure out whether Dev10 is currently installed using the following heuristic:
548                         // - Check whether the overall key (installed if any version of Dev10 is installed) is there.
549                         //   - If it's not, no version of Dev10 exists or has ever existed on this machine, so return 'false'.
550                         //   - If it is, we know that some version of Dev10 has been installed at some point, but we don't know
551                         //     for sure whether it's still there or not.  Check the inndividual keys for {Pro, Premium, Ultimate,
552                         //     C# Express, VB Express, C++ Express, VWD Express, LightSwitch} 2010
553                         //     - If even one of them exists, return 'true'.
554                         //     - Otherwise, return 'false.
555                         if (!RegistryKeyWrapper.KeyExists(Dev10OverallInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32))
556                         {
557                             s_dev10IsInstalled = false;
558                         }
559                         else if (
560                                     RegistryKeyWrapper.KeyExists(Dev10UltimateInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) ||
561                                     RegistryKeyWrapper.KeyExists(Dev10PremiumInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) ||
562                                     RegistryKeyWrapper.KeyExists(Dev10ProfessionalInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) ||
563                                     RegistryKeyWrapper.KeyExists(Dev10VCSExpressInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) ||
564                                     RegistryKeyWrapper.KeyExists(Dev10VBExpressInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) ||
565                                     RegistryKeyWrapper.KeyExists(Dev10VCExpressInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) ||
566                                     RegistryKeyWrapper.KeyExists(Dev10VWDExpressInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32) ||
567                                     RegistryKeyWrapper.KeyExists(Dev10LightSwitchInstallKeyRegistryPath, RegistryHive.LocalMachine, RegistryView.Registry32)
568                                 )
569                         {
570                             s_dev10IsInstalled = true;
571                         }
572                         else
573                         {
574                             s_dev10IsInstalled = false;
575                         }
576                     }
577                     catch (Exception e)
578                     {
579                         if (ExceptionHandling.NotExpectedRegistryException(e))
580                         {
581                             throw;
582                         }
583 
584                         // if it's a registry exception, just shrug, eat it, and move on with life on the assumption that whatever
585                         // went wrong, it's pretty clear that Dev10 probably isn't installed.
586                         s_dev10IsInstalled = false;
587                     }
588                 }
589 
590                 return s_dev10IsInstalled.Value;
591 #else
592                 return false;
593 #endif
594             }
595         }
596 
597         /// <summary>
598         /// Path to look for msbuild override task files.
599         /// </summary>
600         internal string OverrideTasksPath => _overrideTasksPath;
601 
602         /// <summary>
603         /// ToolsVersion to use as the default ToolsVersion for this version of MSBuild
604         /// </summary>
605         internal string DefaultOverrideToolsVersion => _defaultOverrideToolsVersion;
606 
607         /// <summary>
608         /// Map of properties to their list of fall-back search paths
609         /// </summary>
610         internal Dictionary<string, ProjectImportPathMatch> ImportPropertySearchPathsTable => _propertySearchPathsTable;
611 
612         /// <summary>
613         /// Map of MSBuildExtensionsPath properties to their list of fallback search paths
614         /// </summary>
615         internal Dictionary<MSBuildExtensionsPathReferenceKind, IList<string>> MSBuildExtensionsPathSearchPathsTable
616         {
617             get; set;
618         }
619 
620         /// <summary>
621         /// Function for serialization.
622         /// </summary>
INodePacketTranslatable.Translate(INodePacketTranslator translator)623         void INodePacketTranslatable.Translate(INodePacketTranslator translator)
624         {
625             translator.Translate(ref _toolsVersion);
626             translator.Translate(ref _toolsPath);
627             translator.TranslateProjectPropertyInstanceDictionary(ref _properties);
628             translator.TranslateProjectPropertyInstanceDictionary(ref _environmentProperties);
629             translator.TranslateProjectPropertyInstanceDictionary(ref _globalProperties);
630             translator.TranslateDictionary(ref _subToolsets, StringComparer.OrdinalIgnoreCase, SubToolset.FactoryForDeserialization);
631             translator.Translate(ref _overrideTasksPath);
632             translator.Translate(ref _defaultOverrideToolsVersion);
633             translator.TranslateDictionary(ref _propertySearchPathsTable, StringComparer.OrdinalIgnoreCase, ProjectImportPathMatch.FactoryForDeserialization);
634         }
635 
636         /// <summary>
637         /// Generates the sub-toolset version to be used with this toolset.  Sub-toolset version is based on:
638         /// 1. If "VisualStudioVersion" is set as a property on the toolset itself (global or environment),
639         ///    use that.
640         /// 2. Otherwise, use the default sub-toolset version for this toolset.
641         ///
642         /// The sub-toolset version returned may be null; if so, that means that no sub-toolset should be used,
643         /// just the base toolset on its own. The sub-toolset version returned may not map to an existing
644         /// sub-toolset.
645         /// </summary>
GenerateSubToolsetVersion()646         public string GenerateSubToolsetVersion()
647         {
648             string subToolsetVersion = GenerateSubToolsetVersion(0 /* user doesn't care about solution version */);
649             return subToolsetVersion;
650         }
651 
652         /// <summary>
653         /// Generates the sub-toolset version to be used with this toolset.  Sub-toolset version is based on:
654         /// 1. If the "VisualStudioVersion" global property exists in the set of properties passed to us, use it.
655         /// 2. Otherwise, if "VisualStudioVersion" is set as a property on the toolset itself (global or environment),
656         ///    use that.
657         /// 3. Otherwise, use Visual Studio version from solution file if it maps to an existing sub-toolset.
658         /// 4. Otherwise, use the default sub-toolset version for this toolset.
659         ///
660         /// The sub-toolset version returned may be null; if so, that means that no sub-toolset should be used,
661         /// just the base toolset on its own. The sub-toolset version returned may not map to an existing
662         /// sub-toolset.
663         ///
664         /// The global properties dictionary may be null.
665         /// </summary>
666         [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "solutionVersion-1", Justification = "Method called in restricted places. Checks done by the callee and inside the method.")]
GenerateSubToolsetVersion(IDictionary<string, string> overrideGlobalProperties, int solutionVersion)667         public string GenerateSubToolsetVersion(IDictionary<string, string> overrideGlobalProperties, int solutionVersion)
668         {
669             return GenerateSubToolsetVersionUsingVisualStudioVersion(overrideGlobalProperties, solutionVersion - 1);
670         }
671 
672         /// <summary>
673         /// Given a property name and a sub-toolset version, searches for that property first in the
674         /// sub-toolset, then falls back to the base toolset if necessary, and returns the property
675         /// if it was found.
676         /// </summary>
GetProperty(string propertyName, string subToolsetVersion)677         public ProjectPropertyInstance GetProperty(string propertyName, string subToolsetVersion)
678         {
679             SubToolset subToolset;
680             ProjectPropertyInstance property = null;
681 
682             if (SubToolsets.TryGetValue(subToolsetVersion, out subToolset))
683             {
684                 property = subToolset.Properties[propertyName];
685             }
686 
687             return property ?? (Properties[propertyName]);
688         }
689 
690         /// <summary>
691         /// Factory for deserialization.
692         /// </summary>
FactoryForDeserialization(INodePacketTranslator translator)693         internal static Toolset FactoryForDeserialization(INodePacketTranslator translator)
694         {
695             Toolset toolset = new Toolset(translator);
696             return toolset;
697         }
698 
699         /// <summary>
700         /// Given a search path and a task pattern get a list of task or override task files.
701         /// </summary>
GetTaskFiles(DirectoryGetFiles getFiles, ILoggingService loggingServices, BuildEventContext buildEventContext, string taskPattern, string searchPath, string taskFileWarning)702         internal static string[] GetTaskFiles(DirectoryGetFiles getFiles, ILoggingService loggingServices, BuildEventContext buildEventContext, string taskPattern, string searchPath, string taskFileWarning)
703         {
704             string[] defaultTasksFiles = null;
705 
706             try
707             {
708                 if (null != getFiles)
709                 {
710                     defaultTasksFiles = getFiles(searchPath, taskPattern);
711                 }
712                 else
713                 {
714                     // The order of the returned file names is not guaranteed per msdn
715                     defaultTasksFiles = Directory.GetFiles(searchPath, taskPattern);
716                 }
717 
718                 if (defaultTasksFiles.Length == 0)
719                 {
720                     loggingServices.LogWarning
721                         (
722                         buildEventContext,
723                         null,
724                         new BuildEventFileInfo(/* this warning truly does not involve any file */ String.Empty),
725                         taskFileWarning,
726                         taskPattern,
727                         searchPath,
728                         String.Empty
729                         );
730                 }
731             }
732             catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
733             {
734                 loggingServices.LogWarning
735                     (
736                     buildEventContext,
737                     null,
738                     new BuildEventFileInfo(/* this warning truly does not involve any file */ String.Empty),
739                     taskFileWarning,
740                     taskPattern,
741                     searchPath,
742                     e.Message
743                     );
744             }
745 
746             // Sort the file names to give a deterministic order
747             if (defaultTasksFiles != null)
748             {
749                 Array.Sort<string>(defaultTasksFiles, StringComparer.OrdinalIgnoreCase);
750                 return defaultTasksFiles;
751             }
752             return Array.Empty<string>();
753         }
754 
755         /// <summary>
756         /// Generates the sub-toolset version to be used with this toolset.  Sub-toolset version is based on:
757         /// 1. If the "VisualStudioVersion" global property exists in the set of properties passed to us, use it.
758         /// 2. Otherwise, if "VisualStudioVersion" is set as a property on the toolset itself (global or environment),
759         ///    use that.
760         /// 3. Otherwise, use Visual Studio version from solution file if it maps to an existing sub-toolset.
761         /// 4. Otherwise, use the default sub-toolset version for this toolset.
762         ///
763         /// The sub-toolset version returned may be null; if so, that means that no sub-toolset should be used,
764         /// just the base toolset on its own. The sub-toolset version returned may not map to an existing
765         /// sub-toolset.
766         ///
767         /// The global properties dictionary may be null.
768         /// </summary>
GenerateSubToolsetVersion(PropertyDictionary<ProjectPropertyInstance> overrideGlobalProperties)769         internal string GenerateSubToolsetVersion(PropertyDictionary<ProjectPropertyInstance> overrideGlobalProperties)
770         {
771             ProjectPropertyInstance subToolsetProperty = null;
772             string visualStudioVersion = null;
773             if (overrideGlobalProperties != null)
774             {
775                 subToolsetProperty = overrideGlobalProperties[Constants.SubToolsetVersionPropertyName];
776 
777                 if (subToolsetProperty != null)
778                 {
779                     visualStudioVersion = subToolsetProperty.EvaluatedValue;
780                     return visualStudioVersion;
781                 }
782             }
783 
784             visualStudioVersion = GenerateSubToolsetVersion(0 /* don't care about solution version */);
785             return visualStudioVersion;
786         }
787 
788         /// <summary>
789         /// Generates the sub-toolset version to be used with this toolset.  Sub-toolset version is based on:
790         /// 1. If the "VisualStudioVersion" global property exists in the set of properties passed to us, use it.
791         /// 2. Otherwise, if "VisualStudioVersion" is set as a property on the toolset itself (global or environment),
792         ///    use that.
793         /// 3. Otherwise, use Visual Studio version from solution file if it maps to an existing sub-toolset.
794         /// 4. Otherwise, use the default sub-toolset version for this toolset.
795         ///
796         /// The sub-toolset version returned may be null; if so, that means that no sub-toolset should be used,
797         /// just the base toolset on its own. The sub-toolset version returned may not map to an existing
798         /// sub-toolset.
799         ///
800         /// The global properties dictionary may be null.
801         /// </summary>
GenerateSubToolsetVersion(int visualStudioVersionFromSolution)802         internal string GenerateSubToolsetVersion(int visualStudioVersionFromSolution)
803         {
804             // Next, try the toolset global properties (before environment properties because if there's a clash between the
805             // two, global should win)
806             if (_globalProperties != null)
807             {
808                 ProjectPropertyInstance visualStudioVersionProperty = _globalProperties[Constants.SubToolsetVersionPropertyName];
809 
810                 if (visualStudioVersionProperty != null)
811                 {
812                     return visualStudioVersionProperty.EvaluatedValue;
813                 }
814             }
815 
816             // Next, try the toolset environment properties
817             if (_environmentProperties != null)
818             {
819                 ProjectPropertyInstance visualStudioVersionProperty = _environmentProperties[Constants.SubToolsetVersionPropertyName];
820 
821                 if (visualStudioVersionProperty != null)
822                 {
823                     return visualStudioVersionProperty.EvaluatedValue;
824                 }
825             }
826 
827             // The VisualStudioVersion derived from parsing the solution version in the solution file
828             string subToolsetVersion = null;
829             if (visualStudioVersionFromSolution > 0)
830             {
831                 Version visualStudioVersionFromSolutionAsVersion = new Version(visualStudioVersionFromSolution, 0);
832                 subToolsetVersion = SubToolsets.Keys.FirstOrDefault(version => visualStudioVersionFromSolutionAsVersion.Equals(VersionUtilities.ConvertToVersion(version)));
833             }
834 
835             // Solution version also didn't work out, so fall back to default.
836             // If subToolsetVersion is null, there simply wasn't a matching solution version.
837             return subToolsetVersion ?? (DefaultSubToolsetVersion);
838         }
839 
840         /// <summary>
841         /// Return a task registry stub for the tasks in the *.tasks file for this toolset
842         /// </summary>
843         /// <param name="loggingServices">The logging services used to log during task registration.</param>
844         /// <param name="buildEventContext">The build event context used to log during task registration.</param>
845         /// <param name="projectRootElementCache">The <see cref="ProjectRootElementCache"/> to use.</param>
846         /// <returns>The task registry</returns>
GetTaskRegistry(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)847         internal TaskRegistry GetTaskRegistry(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)
848         {
849             RegisterDefaultTasks(loggingServices, buildEventContext, projectRootElementCache);
850             return _defaultTaskRegistry;
851         }
852 
853         /// <summary>
854         /// Get SubToolset version using Visual Studio version from Dev 12 solution file
855         /// </summary>
GenerateSubToolsetVersionUsingVisualStudioVersion(IDictionary<string, string> overrideGlobalProperties, int visualStudioVersionFromSolution)856         internal string GenerateSubToolsetVersionUsingVisualStudioVersion(IDictionary<string, string> overrideGlobalProperties, int visualStudioVersionFromSolution)
857         {
858             string visualStudioVersion = null;
859             if (overrideGlobalProperties != null && overrideGlobalProperties.TryGetValue(Constants.SubToolsetVersionPropertyName, out visualStudioVersion))
860             {
861                 return visualStudioVersion;
862             }
863 
864             visualStudioVersion = GenerateSubToolsetVersion(visualStudioVersionFromSolution);
865             return visualStudioVersion;
866         }
867 
868         /// <summary>
869         /// Return a task registry for the override tasks in the *.overridetasks file for this toolset
870         /// </summary>
871         /// <param name="loggingServices">The logging services used to log during task registration.</param>
872         /// <param name="buildEventContext">The build event context used to log during task registration.</param>
873         /// <param name="projectRootElementCache">The <see cref="ProjectRootElementCache"/> to use.</param>
874         /// <returns>The task registry</returns>
GetOverrideTaskRegistry(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)875         internal TaskRegistry GetOverrideTaskRegistry(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)
876         {
877             RegisterOverrideTasks(loggingServices, buildEventContext, projectRootElementCache);
878             return _overrideTaskRegistry;
879         }
880 
881         /// <summary>
882         /// Used to load information about default MSBuild tasks i.e. tasks that do not need to be explicitly declared in projects
883         /// with the &lt;UsingTask&gt; element. Default task information is read from special files, which are located in the same
884         /// directory as the MSBuild binaries.
885         /// </summary>
886         /// <remarks>
887         /// 1) a default tasks file needs the &lt;Project&gt; root tag in order to be well-formed
888         /// 2) the XML declaration tag &lt;?xml ...&gt; is ignored
889         /// 3) comment tags are always ignored regardless of their placement
890         /// 4) the rest of the tags are expected to be &lt;UsingTask&gt; tags
891         /// </remarks>
892         /// <param name="loggingServices">The logging services to use to log during this registration.</param>
893         /// <param name="buildEventContext">The build event context to use to log during this registration.</param>
894         /// <param name="projectRootElementCache">The <see cref="ProjectRootElementCache"/> to use.</param>
RegisterDefaultTasks(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)895         private void RegisterDefaultTasks(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)
896         {
897             if (!_defaultTasksRegistrationAttempted)
898             {
899                 try
900                 {
901                     _defaultTaskRegistry = new TaskRegistry(projectRootElementCache);
902 
903                     InitializeProperties(loggingServices, buildEventContext);
904 
905                     string[] defaultTasksFiles = GetTaskFiles(_getFiles, loggingServices, buildEventContext, DefaultTasksFilePattern, ToolsPath, "DefaultTasksFileLoadFailureWarning");
906                     LoadAndRegisterFromTasksFile(ToolsPath, defaultTasksFiles, loggingServices, buildEventContext, DefaultTasksFilePattern, "DefaultTasksFileFailure", projectRootElementCache, _defaultTaskRegistry);
907                 }
908                 finally
909                 {
910                     _defaultTasksRegistrationAttempted = true;
911                 }
912             }
913         }
914 
915         /// <summary>
916         /// Initialize the properties which are used to evaluate the tasks files.
917         /// </summary>
InitializeProperties(ILoggingService loggingServices, BuildEventContext buildEventContext)918         private void InitializeProperties(ILoggingService loggingServices, BuildEventContext buildEventContext)
919         {
920             try
921             {
922                 if (_propertyBag == null)
923                 {
924                     List<ProjectPropertyInstance> reservedProperties = new List<ProjectPropertyInstance>();
925 
926                     reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.binPath, EscapingUtilities.Escape(ToolsPath), mayBeReserved: true));
927                     reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.toolsVersion, ToolsVersion, mayBeReserved: true));
928 
929                     reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.toolsPath, EscapingUtilities.Escape(ToolsPath), mayBeReserved: true));
930                     reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.assemblyVersion, Constants.AssemblyVersion, mayBeReserved: true));
931                     reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.version, MSBuildAssemblyFileVersion.Instance.MajorMinorBuild, mayBeReserved: true));
932 
933                     // Add one for the subtoolset version property -- it may or may not be set depending on whether it has already been set by the
934                     // environment or global properties, but it's better to create a dictionary that's one too big than one that's one too small.
935                     int count = _environmentProperties.Count + reservedProperties.Count + Properties.Values.Count + _globalProperties.Count + 1;
936 
937                     // GenerateSubToolsetVersion checks the environment and global properties, so it's safe to go ahead and gather the
938                     // subtoolset properties here without fearing that we'll have somehow come up with the wrong subtoolset version.
939                     string subToolsetVersion = this.GenerateSubToolsetVersion();
940                     SubToolset subToolset;
941                     ICollection<ProjectPropertyInstance> subToolsetProperties = null;
942 
943                     if (subToolsetVersion != null)
944                     {
945                         if (SubToolsets.TryGetValue(subToolsetVersion, out subToolset))
946                         {
947                             subToolsetProperties = subToolset.Properties.Values;
948                             count += subToolsetProperties.Count;
949                         }
950                     }
951 
952                     _propertyBag = new PropertyDictionary<ProjectPropertyInstance>(count);
953 
954                     // Should be imported in the same order as in the evaluator:
955                     // - Environment
956                     // - Toolset
957                     // - Subtoolset (if any)
958                     // - Global
959                     _propertyBag.ImportProperties(_environmentProperties);
960 
961                     _propertyBag.ImportProperties(reservedProperties);
962 
963                     _propertyBag.ImportProperties(Properties.Values);
964 
965                     if (subToolsetVersion != null)
966                     {
967                         _propertyBag.Set(ProjectPropertyInstance.Create(Constants.SubToolsetVersionPropertyName, subToolsetVersion));
968                     }
969 
970                     if (subToolsetProperties != null)
971                     {
972                         _propertyBag.ImportProperties(subToolsetProperties);
973                     }
974 
975                     _propertyBag.ImportProperties(_globalProperties);
976                 }
977 
978                 if (_expander == null)
979                 {
980                     _expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(_propertyBag);
981                 }
982             }
983             catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
984             {
985                 loggingServices.LogError(buildEventContext, new BuildEventFileInfo(/* this warning truly does not involve any file it is just gathering properties */String.Empty), "TasksPropertyBagError", e.Message);
986             }
987         }
988 
989         /// <summary>
990         /// Used to load information about MSBuild override tasks i.e. tasks that override tasks declared in tasks or project files.
991         /// </summary>
RegisterOverrideTasks(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)992         private void RegisterOverrideTasks(ILoggingService loggingServices, BuildEventContext buildEventContext, ProjectRootElementCache projectRootElementCache)
993         {
994             if (!_overrideTasksRegistrationAttempted)
995             {
996                 try
997                 {
998                     _overrideTaskRegistry = new TaskRegistry(projectRootElementCache);
999                     bool overrideDirectoryExists = false;
1000 
1001                     try
1002                     {
1003                         // Make sure the override directory exists and is not empty before trying to find the files
1004                         if (!String.IsNullOrEmpty(_overrideTasksPath))
1005                         {
1006                             if (Path.IsPathRooted(_overrideTasksPath))
1007                             {
1008                                 if (null != _directoryExists)
1009                                 {
1010                                     overrideDirectoryExists = _directoryExists(_overrideTasksPath);
1011                                 }
1012                                 else
1013                                 {
1014                                     overrideDirectoryExists = Directory.Exists(_overrideTasksPath);
1015                                 }
1016                             }
1017 
1018                             if (!overrideDirectoryExists)
1019                             {
1020                                 string rootedPathMessage = ResourceUtilities.FormatResourceString("OverrideTaskNotRootedPath", _overrideTasksPath);
1021                                 loggingServices.LogWarning(buildEventContext, null, new BuildEventFileInfo(String.Empty /* this warning truly does not involve any file*/), "OverrideTasksFileFailure", rootedPathMessage);
1022                             }
1023                         }
1024                     }
1025                     catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
1026                     {
1027                         string rootedPathMessage = ResourceUtilities.FormatResourceString("OverrideTaskProblemWithPath", _overrideTasksPath, e.Message);
1028                         loggingServices.LogWarning(buildEventContext, null, new BuildEventFileInfo(String.Empty /* this warning truly does not involve any file*/), "OverrideTasksFileFailure", rootedPathMessage);
1029                     }
1030 
1031                     if (overrideDirectoryExists)
1032                     {
1033                         InitializeProperties(loggingServices, buildEventContext);
1034                         string[] overrideTasksFiles = GetTaskFiles(_getFiles, loggingServices, buildEventContext, OverrideTasksFilePattern, _overrideTasksPath, "OverrideTasksFileLoadFailureWarning");
1035 
1036                         // Load and register any override tasks
1037                         LoadAndRegisterFromTasksFile(_overrideTasksPath, overrideTasksFiles, loggingServices, buildEventContext, OverrideTasksFilePattern, "OverrideTasksFileFailure", projectRootElementCache, _overrideTaskRegistry);
1038                     }
1039                 }
1040                 finally
1041                 {
1042                     _overrideTasksRegistrationAttempted = true;
1043                 }
1044             }
1045         }
1046 
1047         /// <summary>
1048         /// Do the actual loading of the tasks or override tasks file and register the tasks in the task registry
1049         /// </summary>
LoadAndRegisterFromTasksFile(string searchPath, string[] defaultTaskFiles, ILoggingService loggingServices, BuildEventContext buildEventContext, string defaultTasksFilePattern, string taskFileError, ProjectRootElementCache projectRootElementCache, TaskRegistry registry)1050         private void LoadAndRegisterFromTasksFile(string searchPath, string[] defaultTaskFiles, ILoggingService loggingServices, BuildEventContext buildEventContext, string defaultTasksFilePattern, string taskFileError, ProjectRootElementCache projectRootElementCache, TaskRegistry registry)
1051         {
1052             foreach (string defaultTasksFile in defaultTaskFiles)
1053             {
1054                 try
1055                 {
1056                     // Important to keep the following line since unit tests use the delegate.
1057                     ProjectRootElement projectRootElement;
1058                     if (_loadXmlFromPath != null)
1059                     {
1060                         XmlDocumentWithLocation defaultTasks = _loadXmlFromPath(defaultTasksFile);
1061                         projectRootElement = ProjectRootElement.Open(defaultTasks);
1062                     }
1063                     else
1064                     {
1065                         projectRootElement = ProjectRootElement.Open(defaultTasksFile, projectRootElementCache,
1066                             false /*The tasks file is not a explicitly loaded file*/,
1067                             preserveFormatting: false);
1068                     }
1069 
1070                     foreach (ProjectElement elementXml in projectRootElement.Children)
1071                     {
1072                         ProjectUsingTaskElement usingTask = elementXml as ProjectUsingTaskElement;
1073 
1074                         if (null == usingTask)
1075                         {
1076                             ProjectErrorUtilities.ThrowInvalidProject
1077                                 (
1078                                 elementXml.Location,
1079                                 "UnrecognizedElement",
1080                                 elementXml.XmlElement.Name
1081                                 );
1082                         }
1083 
1084                         TaskRegistry.RegisterTasksFromUsingTaskElement<ProjectPropertyInstance, ProjectItemInstance>
1085                             (
1086                             loggingServices,
1087                             buildEventContext,
1088                             Path.GetDirectoryName(defaultTasksFile),
1089                             usingTask,
1090                             registry,
1091                             _expander,
1092                             ExpanderOptions.ExpandProperties
1093                             );
1094                     }
1095                 }
1096                 catch (XmlException e)
1097                 {
1098                     // handle XML errors in the default tasks file
1099                     ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, new BuildEventFileInfo(defaultTasksFile, e), taskFileError, e.Message);
1100                 }
1101                 catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
1102                 {
1103                     loggingServices.LogError(buildEventContext, new BuildEventFileInfo(defaultTasksFile), taskFileError, e.Message);
1104                     break;
1105                 }
1106             }
1107         }
1108     }
1109 }
1110