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 using System;
5 using System.Diagnostics;
6 using System.IO;
7 using System.Linq;
8 using System.Collections.Generic;
9 using System.Text.RegularExpressions;
10 using System.Threading;
11 
12 namespace Microsoft.Build.Shared
13 {
14     internal class BuildEnvironmentHelper
15     {
16         // Since this class is added as 'link' to shared source in multiple projects,
17         // MSBuildConstants.CurrentVisualStudioVersion is not available in all of them.
18         private const string CurrentVisualStudioVersion = "15.0";
19 
20         // MSBuildConstants.CurrentToolsVersion
21         private const string CurrentToolsVersion = "15.0";
22 
23         /// <summary>
24         /// Name of the Visual Studio (and Blend) process.
25         // VS ASP intellisense server fails without Microsoft.VisualStudio.Web.Host. Remove when issue fixed: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/574986
26         /// </summary>
27         private static readonly string[] s_visualStudioProcess = {"DEVENV", "BLEND", "Microsoft.VisualStudio.Web.Host"};
28 
29         /// <summary>
30         /// Name of the MSBuild process(es)
31         /// </summary>
32         private static readonly string[] s_msBuildProcess = {"MSBUILD", "MSBUILDTASKHOST"};
33 
34         /// <summary>
35         /// Name of MSBuild executable files.
36         /// </summary>
37         private static readonly string[] s_msBuildExeNames = { "MSBuild.exe", "MSBuild.dll" };
38 
39         /// <summary>
40         /// Gets the cached Build Environment instance.
41         /// </summary>
42         public static BuildEnvironment Instance
43         {
44             get
45             {
46                 try
47                 {
48                     return BuildEnvironmentHelperSingleton.s_instance;
49                 }
50                 catch (TypeInitializationException e)
51                 {
52                     if (e.InnerException != null)
53                     {
54                         // Throw the error that caused the TypeInitializationException.
55                         // (likely InvalidOperationException)
56                         throw e.InnerException;
57                     }
58 
59                     throw;
60                 }
61             }
62         }
63 
64         /// <summary>
65         /// Find the location of MSBuild.exe based on the current environment.
66         /// </summary>
67         /// <remarks>
68         /// This defines the order and precedence for various methods of discovering MSBuild and associated toolsets.
69         /// At a high level, an install under Visual Studio is preferred as the user may have SDKs installed to a
70         /// specific instance of Visual Studio and build will only succeed if we can discover those. See
71         /// https://github.com/Microsoft/msbuild/issues/1461 for details.
72         /// </remarks>
73         /// <returns>Build environment.</returns>
Initialize()74         private static BuildEnvironment Initialize()
75         {
76             // See https://github.com/Microsoft/msbuild/issues/1461 for specification of ordering and details.
77             var possibleLocations = new Func<BuildEnvironment>[]
78             {
79                 TryFromEnvironmentVariable,
80                 TryFromVisualStudioProcess,
81                 TryFromMSBuildProcess,
82                 TryFromMSBuildAssembly,
83                 TryFromDevConsole,
84                 TryFromSetupApi,
85                 TryFromAppContextBaseDirectory
86             };
87 
88             foreach (var location in possibleLocations)
89             {
90                 var env = location();
91                 if (env != null)
92                     return env;
93             }
94 
95             // If we can't find a suitable environment, continue in the 'None' mode. If not running tests,
96             // we will use the current running process for the CurrentMSBuildExePath value.  This is likely
97             // wrong, but many things use the CurrentMSBuildToolsDirectory value which must be set for basic
98             // functionality to work.
99             //
100             // If we are running tests, then the current running process may be a test runner located in the
101             // NuGet packages folder.  So in that case, we use the location of the current assembly, which
102             // will be in the output path of the test project, which is what we want.
103 
104             string msbuildExePath;
105             if (s_runningTests())
106             {
107                 msbuildExePath = typeof(BuildEnvironmentHelper).Assembly.Location;
108             }
109             else
110             {
111                 msbuildExePath = s_getProcessFromRunningProcess();
112             }
113 
114             return new BuildEnvironment(
115                 BuildEnvironmentMode.None,
116                 msbuildExePath,
117                 runningTests: s_runningTests(),
118                 runningInVisualStudio: false,
119                 visualStudioPath: null);
120         }
121 
TryFromEnvironmentVariable()122         private static BuildEnvironment TryFromEnvironmentVariable()
123         {
124             var msBuildExePath = s_getEnvironmentVariable("MSBUILD_EXE_PATH");
125 
126             return TryFromStandaloneMSBuildExe(msBuildExePath);
127         }
128 
TryFromVisualStudioProcess()129         private static BuildEnvironment TryFromVisualStudioProcess()
130         {
131             if (!NativeMethodsShared.IsWindows)
132                 return null;
133 
134             var vsProcess = s_getProcessFromRunningProcess();
135             if (!IsProcessInList(vsProcess, s_visualStudioProcess)) return null;
136 
137             var vsRoot = FileUtilities.GetFolderAbove(vsProcess, 3);
138             string msBuildExe = GetMSBuildExeFromVsRoot(vsRoot);
139 
140             return new BuildEnvironment(
141                 BuildEnvironmentMode.VisualStudio,
142                 msBuildExe,
143                 runningTests: false,
144                 runningInVisualStudio: true,
145                 visualStudioPath: vsRoot);
146         }
147 
TryFromMSBuildProcess()148         private static BuildEnvironment TryFromMSBuildProcess()
149         {
150             var msBuildExe = s_getProcessFromRunningProcess();
151             if (!IsProcessInList(msBuildExe, s_msBuildProcess)) return null;
152 
153             // First check if we're in a VS installation
154             if (NativeMethodsShared.IsWindows &&
155                 Regex.IsMatch(msBuildExe, $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*MSBuild(?:TaskHost)?\.exe", RegexOptions.IgnoreCase))
156             {
157                 return new BuildEnvironment(
158                     BuildEnvironmentMode.VisualStudio,
159                     msBuildExe,
160                     runningTests: false,
161                     runningInVisualStudio: false,
162                     visualStudioPath: GetVsRootFromMSBuildAssembly(msBuildExe));
163             }
164 
165             // Standalone mode running in MSBuild.exe
166             return new BuildEnvironment(
167                 BuildEnvironmentMode.Standalone,
168                 msBuildExe,
169                 runningTests: false,
170                 runningInVisualStudio: false,
171                 visualStudioPath: null);
172         }
173 
TryFromMSBuildAssembly()174         private static BuildEnvironment TryFromMSBuildAssembly()
175         {
176             var buildAssembly = s_getExecutingAssemblyPath();
177             if (buildAssembly == null) return null;
178 
179             // Check for MSBuild.[exe|dll] next to the current assembly
180             var msBuildExe = Path.Combine(FileUtilities.GetFolderAbove(buildAssembly), "MSBuild.exe");
181             var msBuildDll = Path.Combine(FileUtilities.GetFolderAbove(buildAssembly), "MSBuild.dll");
182 
183             // First check if we're in a VS installation
184             if (NativeMethodsShared.IsWindows &&
185                 Regex.IsMatch(buildAssembly, $@".*\\MSBuild\\{CurrentToolsVersion}\\Bin\\.*", RegexOptions.IgnoreCase))
186             {
187                 // In a Visual Studio path we must have MSBuild.exe
188                 if (File.Exists(msBuildExe))
189                 {
190                     return new BuildEnvironment(
191                         BuildEnvironmentMode.VisualStudio,
192                         msBuildExe,
193                         runningTests: s_runningTests(),
194                         runningInVisualStudio: false,
195                         visualStudioPath: GetVsRootFromMSBuildAssembly(msBuildExe));
196                 }
197             }
198 
199             // We're not in VS, check for MSBuild.exe / dll to consider this a standalone environment.
200             string msBuildPath = null;
201             if (File.Exists(msBuildExe)) msBuildPath = msBuildExe;
202             else if (File.Exists(msBuildDll)) msBuildPath = msBuildDll;
203 
204             if (!string.IsNullOrEmpty(msBuildPath))
205             {
206                 // Standalone mode with toolset
207                 return new BuildEnvironment(
208                     BuildEnvironmentMode.Standalone,
209                     msBuildPath,
210                     runningTests: s_runningTests(),
211                     runningInVisualStudio: false,
212                     visualStudioPath: null);
213             }
214 
215             return null;
216 
217         }
218 
TryFromDevConsole()219         private static BuildEnvironment TryFromDevConsole()
220         {
221             if (s_runningTests())
222             {
223                 //  If running unit tests, then don't try to get the build environment from MSBuild installed on the machine
224                 //  (we should be using the locally built MSBuild instead)
225                 return null;
226             }
227 
228             // VSINSTALLDIR and VisualStudioVersion are set from the Developer Command Prompt.
229             var vsInstallDir = s_getEnvironmentVariable("VSINSTALLDIR");
230             var vsVersion = s_getEnvironmentVariable("VisualStudioVersion");
231 
232             if (string.IsNullOrEmpty(vsInstallDir) || string.IsNullOrEmpty(vsVersion) ||
233                 vsVersion != CurrentVisualStudioVersion || !Directory.Exists(vsInstallDir)) return null;
234 
235             return new BuildEnvironment(
236                 BuildEnvironmentMode.VisualStudio,
237                 GetMSBuildExeFromVsRoot(vsInstallDir),
238                 runningTests: false,
239                 runningInVisualStudio: false,
240                 visualStudioPath: vsInstallDir);
241         }
242 
TryFromSetupApi()243         private static BuildEnvironment TryFromSetupApi()
244         {
245             if (s_runningTests())
246             {
247                 //  If running unit tests, then don't try to get the build environment from MSBuild installed on the machine
248                 //  (we should be using the locally built MSBuild instead)
249                 return null;
250             }
251 
252             Version v = new Version(CurrentVisualStudioVersion);
253             var instances = s_getVisualStudioInstances()
254                 .Where(i => i.Version.Major == v.Major && Directory.Exists(i.Path))
255                 .ToList();
256 
257             if (instances.Count == 0) return null;
258 
259             if (instances.Count > 1)
260             {
261                 // TODO: Warn user somehow. We may have picked the wrong one.
262             }
263 
264             return new BuildEnvironment(
265                 BuildEnvironmentMode.VisualStudio,
266                 GetMSBuildExeFromVsRoot(instances[0].Path),
267                 runningTests: false,
268                 runningInVisualStudio: false,
269                 visualStudioPath: instances[0].Path);
270         }
271 
TryFromAppContextBaseDirectory()272         private static BuildEnvironment TryFromAppContextBaseDirectory()
273         {
274             // Assemblies compiled against anything older than .NET 4.0 won't have a System.AppContext
275             // Try the base directory that the assembly resolver uses to probe for assemblies.
276             // Under certain scenarios the assemblies are loaded from spurious locations like the NuGet package cache
277             // but the toolset files are copied to the app's directory via "contentFiles".
278 
279             var appContextBaseDirectory = s_getAppContextBaseDirectory();
280             if (string.IsNullOrEmpty(appContextBaseDirectory)) return null;
281 
282             // Look for possible MSBuild exe names in the AppContextBaseDirectory
283             return s_msBuildExeNames
284                 .Select((name) => TryFromStandaloneMSBuildExe(Path.Combine(appContextBaseDirectory, name)))
285                 .FirstOrDefault(env => env != null);
286         }
287 
TryFromStandaloneMSBuildExe(string msBuildExePath)288         private static BuildEnvironment TryFromStandaloneMSBuildExe(string msBuildExePath)
289         {
290             if (!string.IsNullOrEmpty(msBuildExePath) && File.Exists(msBuildExePath))
291             {
292                 // MSBuild.exe was found outside of Visual Studio. Assume Standalone mode.
293                 return new BuildEnvironment(
294                     BuildEnvironmentMode.Standalone,
295                     msBuildExePath,
296                     runningTests: s_runningTests(),
297                     runningInVisualStudio: false,
298                     visualStudioPath: null);
299             }
300 
301             return null;
302         }
303 
GetVsRootFromMSBuildAssembly(string msBuildAssembly)304         private static string GetVsRootFromMSBuildAssembly(string msBuildAssembly)
305         {
306             return FileUtilities.GetFolderAbove(msBuildAssembly,
307                 Regex.IsMatch(msBuildAssembly, $@".\\MSBuild\\{CurrentToolsVersion}\\Bin\\Amd64\\MSBuild\.exe", RegexOptions.IgnoreCase)
308                     ? 5
309                     : 4);
310         }
311 
GetMSBuildExeFromVsRoot(string visualStudioRoot)312         private static string GetMSBuildExeFromVsRoot(string visualStudioRoot)
313         {
314             return FileUtilities.CombinePaths(visualStudioRoot, "MSBuild", CurrentToolsVersion, "Bin", "MSBuild.exe");
315         }
316 
317         private static bool? _runningTests;
318         private static readonly object _runningTestsLock = new object();
319 
CheckIfRunningTests()320         private static bool CheckIfRunningTests()
321         {
322             if (_runningTests != null)
323             {
324                 return _runningTests.Value;
325             }
326 
327             lock (_runningTestsLock)
328             {
329                 if (_runningTests != null)
330                 {
331                     return _runningTests.Value;
332                 }
333 
334                 //  Check if running tests via the TestInfo class in Microsoft.Build.Framework.
335                 //  See the comments on the TestInfo class for an explanation of why it works this way.
336                 var frameworkAssembly = typeof(Framework.ITask).Assembly;
337                 var testInfoType = frameworkAssembly.GetType("Microsoft.Build.Framework.TestInfo");
338                 var runningTestsField = testInfoType.GetField("s_runningTests", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
339 
340                 _runningTests = (bool)runningTestsField.GetValue(null);
341 
342                 return _runningTests.Value;
343             }
344         }
345 
346         /// <summary>
347         /// Returns true if processName appears in the processList
348         /// </summary>
349         /// <param name="processName">Name of the process</param>
350         /// <param name="processList">List of processes to check</param>
351         /// <returns></returns>
IsProcessInList(string processName, string[] processList)352         private static bool IsProcessInList(string processName, string[] processList)
353         {
354             var processFileName = Path.GetFileNameWithoutExtension(processName);
355 
356             if (string.IsNullOrEmpty(processFileName))
357             {
358                 return false;
359             }
360 
361             return processList.Any(s =>
362                 processFileName.Equals(s, StringComparison.OrdinalIgnoreCase));
363         }
364 
GetProcessFromRunningProcess()365         private static string GetProcessFromRunningProcess()
366         {
367 #if RUNTIME_TYPE_NETCORE
368             return AssemblyUtilities.GetAssemblyLocation(AssemblyUtilities.EntryAssembly);
369 #else
370             return Process.GetCurrentProcess().MainModule.FileName;
371 #endif
372         }
373 
GetExecutingAssemblyPath()374         private static string GetExecutingAssemblyPath()
375         {
376             return FileUtilities.ExecutingAssemblyPath;
377         }
378 
GetAppContextBaseDirectory()379         private static string GetAppContextBaseDirectory()
380         {
381 #if !CLR2COMPATIBILITY // Assemblies compiled against anything older than .NET 4.0 won't have a System.AppContext
382             return AppContext.BaseDirectory;
383 #else
384             return null;
385 #endif
386         }
387 
GetEnvironmentVariable(string variable)388         private static string GetEnvironmentVariable(string variable)
389         {
390             return Environment.GetEnvironmentVariable(variable);
391         }
392 
393         /// <summary>
394         /// Resets the current singleton instance (for testing).
395         /// </summary>
ResetInstance_ForUnitTestsOnly(Func<string> getProcessFromRunningProcess = null, Func<string> getExecutingAssemblyPath = null, Func<string> getAppContextBaseDirectory = null, Func<IEnumerable<VisualStudioInstance>> getVisualStudioInstances = null, Func<string, string> getEnvironmentVariable = null, Func<bool> runningTests = null)396         internal static void ResetInstance_ForUnitTestsOnly(Func<string> getProcessFromRunningProcess = null,
397             Func<string> getExecutingAssemblyPath = null, Func<string> getAppContextBaseDirectory = null,
398             Func<IEnumerable<VisualStudioInstance>> getVisualStudioInstances = null,
399             Func<string, string> getEnvironmentVariable = null,
400             Func<bool> runningTests = null)
401         {
402             s_getProcessFromRunningProcess = getProcessFromRunningProcess ?? GetProcessFromRunningProcess;
403             s_getExecutingAssemblyPath = getExecutingAssemblyPath ?? GetExecutingAssemblyPath;
404             s_getAppContextBaseDirectory = getAppContextBaseDirectory ?? GetAppContextBaseDirectory;
405             s_getVisualStudioInstances = getVisualStudioInstances ?? VisualStudioLocationHelper.GetInstances;
406             s_getEnvironmentVariable = getEnvironmentVariable ?? GetEnvironmentVariable;
407 
408             //  Tests which specifically test the BuildEnvironmentHelper need it to be able to act as if it is not running tests
409             s_runningTests = runningTests ?? CheckIfRunningTests;
410 
411             BuildEnvironmentHelperSingleton.s_instance = Initialize();
412         }
413 
414         private static Func<string> s_getProcessFromRunningProcess = GetProcessFromRunningProcess;
415         private static Func<string> s_getExecutingAssemblyPath = GetExecutingAssemblyPath;
416         private static Func<string> s_getAppContextBaseDirectory = GetAppContextBaseDirectory;
417         private static Func<IEnumerable<VisualStudioInstance>> s_getVisualStudioInstances = VisualStudioLocationHelper.GetInstances;
418         private static Func<string, string> s_getEnvironmentVariable = GetEnvironmentVariable;
419         private static Func<bool> s_runningTests = CheckIfRunningTests;
420 
421 
422         private static class BuildEnvironmentHelperSingleton
423         {
424             // Explicit static constructor to tell C# compiler
425             // not to mark type as beforefieldinit
BuildEnvironmentHelperSingleton()426             static BuildEnvironmentHelperSingleton()
427             { }
428 
429             public static BuildEnvironment s_instance = Initialize();
430         }
431     }
432 
433     /// <summary>
434     /// Enum which defines which environment / mode MSBuild is currently running.
435     /// </summary>
436     internal enum BuildEnvironmentMode
437     {
438         /// <summary>
439         /// Running from Visual Studio directly or from MSBuild installed under an instance of Visual Studio.
440         /// Toolsets and extensions will be loaded from the Visual Studio instance.
441         /// </summary>
442         VisualStudio,
443 
444         /// <summary>
445         /// Running in a standalone toolset mode. All toolsets and extensions paths are relative to the app
446         /// running and not dependent on Visual Studio. (e.g. dotnet CLI, open source clone of our repo)
447         /// </summary>
448         Standalone,
449 
450         /// <summary>
451         /// Running without any defined toolsets. Most functionality limited. Likely will not be able to
452         /// build or evaluate a project. (e.g. reference to Microsoft.*.dll without a toolset definition
453         /// or Visual Studio instance installed).
454         /// </summary>
455         None
456     }
457 
458     /// <summary>
459     /// Defines the current environment for build tools.
460     /// </summary>
461     internal class BuildEnvironment
462     {
BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, bool runningTests, bool runningInVisualStudio, string visualStudioPath)463         public BuildEnvironment(BuildEnvironmentMode mode, string currentMSBuildExePath, bool runningTests, bool runningInVisualStudio, string visualStudioPath)
464         {
465             FileInfo currentMSBuildExeFile = null;
466             DirectoryInfo currentToolsDirectory = null;
467 
468             Mode = mode;
469             RunningTests = runningTests;
470             RunningInVisualStudio = runningInVisualStudio;
471             CurrentMSBuildExePath = currentMSBuildExePath;
472             VisualStudioInstallRootDirectory = visualStudioPath;
473 
474             if (!string.IsNullOrEmpty(currentMSBuildExePath))
475             {
476                 currentMSBuildExeFile = new FileInfo(currentMSBuildExePath);
477                 currentToolsDirectory = currentMSBuildExeFile.Directory;
478 
479                 CurrentMSBuildToolsDirectory = currentMSBuildExeFile.DirectoryName;
480                 CurrentMSBuildConfigurationFile = string.Concat(currentMSBuildExePath, ".config");
481                 MSBuildToolsDirectory32 = CurrentMSBuildToolsDirectory;
482                 MSBuildToolsDirectory64 = CurrentMSBuildToolsDirectory;
483             }
484 
485             // We can't detect an environment, don't try to set other paths.
486             if (mode == BuildEnvironmentMode.None || currentMSBuildExeFile == null || currentToolsDirectory == null)
487                 return;
488 
489             // Check to see if our current folder is 'amd64'
490             bool runningInAmd64 = string.Equals(currentToolsDirectory.Name, "amd64", StringComparison.OrdinalIgnoreCase);
491 
492             var msBuildExeName = currentMSBuildExeFile.Name;
493             var folderAbove = currentToolsDirectory.Parent?.FullName;
494 
495             if (folderAbove != null)
496             {
497                 // Calculate potential paths to other architecture MSBuild.exe
498                 var potentialAmd64FromX86 = FileUtilities.CombinePaths(CurrentMSBuildToolsDirectory, "amd64", msBuildExeName);
499                 var potentialX86FromAmd64 = Path.Combine(folderAbove, msBuildExeName);
500 
501                 // Check for existence of an MSBuild file. Note this is not necessary in a VS installation where we always want to
502                 // assume the correct layout.
503                 var existsCheck = mode == BuildEnvironmentMode.VisualStudio ? new Func<string, bool>(_ => true) : File.Exists;
504 
505                 // Running in amd64 folder and the X86 path is valid
506                 if (runningInAmd64 && existsCheck(potentialX86FromAmd64))
507                 {
508                     MSBuildToolsDirectory32 = folderAbove;
509                     MSBuildToolsDirectory64 = CurrentMSBuildToolsDirectory;
510                 }
511                 // Not running in amd64 folder and the amd64 path is valid
512                 else if (!runningInAmd64 && existsCheck(potentialAmd64FromX86))
513                 {
514                     MSBuildToolsDirectory32 = CurrentMSBuildToolsDirectory;
515                     MSBuildToolsDirectory64 = Path.Combine(CurrentMSBuildToolsDirectory, "amd64");
516                 }
517             }
518 
519             MSBuildExtensionsPath = mode == BuildEnvironmentMode.VisualStudio
520                 ? Path.Combine(VisualStudioInstallRootDirectory, "MSBuild")
521                 : MSBuildToolsDirectory32;
522         }
523 
524         internal BuildEnvironmentMode Mode { get; }
525 
526         /// <summary>
527         /// Gets the flag that indicates if we are running in a test harness.
528         /// </summary>
529         internal bool RunningTests { get; }
530 
531         /// <summary>
532         /// Returns true when the entry point application is Visual Studio.
533         /// </summary>
534         internal bool RunningInVisualStudio { get; }
535 
536         /// <summary>
537         /// Path to the MSBuild 32-bit tools directory.
538         /// </summary>
539         internal string MSBuildToolsDirectory32 { get; }
540 
541         /// <summary>
542         /// Path to the MSBuild 64-bit (AMD64) tools directory.
543         /// </summary>
544         internal string MSBuildToolsDirectory64 { get; }
545 
546         /// <summary>
547         /// Path to the Sdks folder for this MSBuild instance.
548         /// </summary>
549         internal string MSBuildSDKsPath
550         {
551             get
552             {
553                 string defaultSdkPath;
554 
555                 if (VisualStudioInstallRootDirectory != null)
556                 {
557                     // Can't use the N-argument form of Combine because it doesn't exist on .NET 3.5
558                     defaultSdkPath = FileUtilities.CombinePaths(VisualStudioInstallRootDirectory, "MSBuild", "Sdks");
559                 }
560                 else
561                 {
562                     defaultSdkPath = Path.Combine(CurrentMSBuildToolsDirectory, "Sdks");
563                 }
564 
565                 // Allow an environment-variable override of the default SDK location
566                 return Environment.GetEnvironmentVariable("MSBuildSDKsPath") ?? defaultSdkPath;
567             }
568         }
569 
570         /// <summary>
571         /// Full path to the current MSBuild configuration file.
572         /// </summary>
573         internal string CurrentMSBuildConfigurationFile { get; }
574 
575         /// <summary>
576         /// Full path to current MSBuild.exe.
577         /// <remarks>
578         /// This path is likely not the current running process. We may be inside
579         /// Visual Studio or a test harness. In that case this will point to the
580         /// version of MSBuild found to be associated with the current environment.
581         /// </remarks>
582         /// </summary>
583         internal string CurrentMSBuildExePath { get; private set; }
584 
585         /// <summary>
586         /// Full path to the current MSBuild tools directory. This will be 32-bit unless
587         /// we're executing from the 'AMD64' folder.
588         /// </summary>
589         internal string CurrentMSBuildToolsDirectory { get; }
590 
591         /// <summary>
592         /// Path to the root Visual Studio install directory
593         /// (e.g. 'c:\Program Files (x86)\Microsoft Visual Studio 15.0')
594         /// </summary>
595         internal string VisualStudioInstallRootDirectory { get; }
596 
597         /// <summary>
598         /// MSBuild extensions path. On Standalone this defaults to the MSBuild folder. In
599         /// VisualStudio mode this folder will be %VSINSTALLDIR%\MSBuild.
600         /// </summary>
601         internal string MSBuildExtensionsPath { get; set; }
602     }
603 }
604