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.Collections.Concurrent;
6 using System.Collections.Generic;
7 #if FEATURE_SYSTEM_CONFIGURATION
8 using System.Configuration;
9 #endif
10 using System.IO;
11 using System.Linq;
12 using System.Runtime.Versioning;
13 
14 using Microsoft.Build.Evaluation;
15 using Microsoft.Win32;
16 
17 #if FEATURE_SYSTEM_CONFIGURATION
18 using PropertyElement = Microsoft.Build.Evaluation.ToolsetElement.PropertyElement;
19 #endif
20 using System.Reflection;
21 using System.Runtime.InteropServices;
22 
23 namespace Microsoft.Build.Shared
24 {
25     /// <summary>
26     /// Used to specify the targeted bitness of the .NET Framework for some methods of FrameworkLocationHelper
27     /// </summary>
28     internal enum DotNetFrameworkArchitecture
29     {
30         /// <summary>
31         /// Indicates the .NET Framework that is currently being run under.
32         /// </summary>
33         Current = 0,
34 
35         /// <summary>
36         /// Indicates the 32-bit .NET Framework
37         /// </summary>
38         Bitness32 = 1,
39 
40         /// <summary>
41         /// Indicates the 64-bit .NET Framework
42         /// </summary>
43         Bitness64 = 2
44     }
45 
46     /// <summary>
47     /// FrameworkLocationHelper provides utility methods for locating .NET Framework and .NET Framework SDK directories and files
48     /// </summary>
49     internal static class FrameworkLocationHelper
50     {
51         #region Constants
52 
53         internal const string dotNetFrameworkIdentifier = ".NETFramework";
54 
55         // .net versions.
56         internal static readonly Version dotNetFrameworkVersion11 = new Version(1, 1);
57         internal static readonly Version dotNetFrameworkVersion20 = new Version(2, 0);
58         internal static readonly Version dotNetFrameworkVersion30 = new Version(3, 0);
59         internal static readonly Version dotNetFrameworkVersion35 = new Version(3, 5);
60         internal static readonly Version dotNetFrameworkVersion40 = new Version(4, 0);
61         internal static readonly Version dotNetFrameworkVersion45 = new Version(4, 5);
62         internal static readonly Version dotNetFrameworkVersion451 = new Version(4, 5, 1);
63         internal static readonly Version dotNetFrameworkVersion452 = new Version(4, 5, 2);
64         internal static readonly Version dotNetFrameworkVersion46 = new Version(4, 6);
65         internal static readonly Version dotNetFrameworkVersion461 = new Version(4, 6, 1);
66         internal static readonly Version dotNetFrameworkVersion462 = new Version(4, 6, 2);
67         internal static readonly Version dotNetFrameworkVersion47 = new Version(4, 7);
68         internal static readonly Version dotNetFrameworkVersion471 = new Version(4, 7, 1);
69 
70         // visual studio versions.
71         internal static readonly Version visualStudioVersion100 = new Version(10, 0);
72         internal static readonly Version visualStudioVersion110 = new Version(11, 0);
73         internal static readonly Version visualStudioVersion120 = new Version(12, 0);
74         internal static readonly Version visualStudioVersion140 = new Version(14, 0);
75         internal static readonly Version visualStudioVersion150 = new Version(15, 0);
76 
77         // keep this up-to-date; always point to the latest visual studio version.
78         internal static readonly Version visualStudioVersionLatest = visualStudioVersion150;
79 
80         private const string dotNetFrameworkRegistryPath = "SOFTWARE\\Microsoft\\.NETFramework";
81         private const string dotNetFrameworkSetupRegistryPath = "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP";
82         private const string dotNetFrameworkSetupRegistryInstalledName = "Install";
83 
84         internal const string fullDotNetFrameworkRegistryKey = "HKEY_LOCAL_MACHINE\\" + dotNetFrameworkRegistryPath;
85         private const string dotNetFrameworkAssemblyFoldersRegistryPath = dotNetFrameworkRegistryPath + "\\AssemblyFolders";
86         private const string referenceAssembliesRegistryValueName = "All Assemblies In";
87 
88         internal const string dotNetFrameworkSdkInstallKeyValueV11 = "SDKInstallRootv1.1";
89         internal static string dotNetFrameworkVersionFolderPrefixV11 = NativeMethodsShared.IsWindows ? "v1.1" : "1.1"; // v1.1 is for Everett.
90         private const string dotNetFrameworkVersionV11 = "v1.1.4322"; // full Everett version to pass to NativeMethodsShared.GetRequestedRuntimeInfo().
91         private const string dotNetFrameworkRegistryKeyV11 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionV11;
92 
93         internal const string dotNetFrameworkSdkInstallKeyValueV20 = "SDKInstallRootv2.0";
94         internal static string dotNetFrameworkVersionFolderPrefixV20 = NativeMethodsShared.IsWindows ? "v2.0" : "2.0"; // v2.0 is for Whidbey.
95         private const string dotNetFrameworkVersionV20 = "v2.0.50727"; // full Whidbey version to pass to NativeMethodsShared.GetRequestedRuntimeInfo().
96         private const string dotNetFrameworkRegistryKeyV20 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionV20;
97 
98         internal static string dotNetFrameworkVersionFolderPrefixV30 = NativeMethodsShared.IsWindows ? "v3.0" : "3.0"; // v3.0 is for WinFx.
99         private static string s_dotNetFrameworkRegistryKeyV30 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV30 + "\\Setup";
100 
101         private const string fallbackDotNetFrameworkSdkRegistryInstallPath = "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows";
102         internal const string fallbackDotNetFrameworkSdkInstallKeyValue = "CurrentInstallFolder";
103 
104         private const string dotNetFrameworkSdkRegistryPathForV35ToolsOnWinSDK70A = @"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.0A\WinSDK-NetFx35Tools-x86";
105         private const string fullDotNetFrameworkSdkRegistryPathForV35ToolsOnWinSDK70A = "HKEY_LOCAL_MACHINE\\" + dotNetFrameworkSdkRegistryPathForV35ToolsOnWinSDK70A;
106 
107         private const string dotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A = @"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0A\WinSDK-NetFx35Tools-x86";
108         private const string fullDotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A = "HKEY_LOCAL_MACHINE\\" + dotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A;
109 
110         internal static string dotNetFrameworkVersionFolderPrefixV35 = NativeMethodsShared.IsWindows ? "v3.5" : "3.5"; // v3.5 is for Orcas.
111         private static string s_dotNetFrameworkRegistryKeyV35 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV35;
112 
113         internal const string fullDotNetFrameworkSdkRegistryKeyV35OnVS10 = fullDotNetFrameworkSdkRegistryPathForV35ToolsOnWinSDK70A;
114         internal const string fullDotNetFrameworkSdkRegistryKeyV35OnVS11 = fullDotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A;
115 
116         internal static string dotNetFrameworkVersionFolderPrefixV40 = NativeMethodsShared.IsWindows ? "v4.0" : "4.0";
117         internal static string dotNetFrameworkVersionFolderPrefixV45 = NativeMethodsShared.IsWindows ? "v4.5" : "4.5";
118 
119         /// <summary>
120         /// Path to the ToolsVersion definitions in the registry
121         /// </summary>
122         private const string ToolsVersionsRegistryPath = @"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
123 
124         #endregion // Constants
125 
126         #region Static member variables
127 
128         /// <summary>
129         /// By default when a root path is not specified we would like to use the program files directory \ reference assemblies\framework as the root location
130         /// to generate the reference assembly paths from.
131         /// </summary>
132 #if FEATURE_SPECIAL_FOLDERS
133         internal static readonly string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
134 #else
135         internal static readonly string programFiles = FileUtilities.GetFolderPath(FileUtilities.SpecialFolder.ProgramFiles);
136 #endif
137         internal static readonly string programFiles32 = GenerateProgramFiles32();
138         internal static readonly string programFiles64 = GenerateProgramFiles64();
139         internal static readonly string programFilesReferenceAssemblyLocation = GenerateProgramFilesReferenceAssemblyRoot();
140 
141         private static string s_fallbackDotNetFrameworkSdkInstallPath;
142 
143         private static string s_pathToV35ToolsInFallbackDotNetFrameworkSdk;
144 
145         private static string s_pathToV4ToolsInFallbackDotNetFrameworkSdk;
146 
147         /// <summary>
148         /// List the supported .net versions.
149         /// </summary>
150         private static readonly DotNetFrameworkSpec[] s_dotNetFrameworkSpecs =
151         {
152             // v1.1
153             new DotNetFrameworkSpecLegacy(
154                 dotNetFrameworkVersion11,
155                 dotNetFrameworkRegistryKeyV11,
156                 dotNetFrameworkSetupRegistryInstalledName,
157                 dotNetFrameworkVersionFolderPrefixV11,
158                 dotNetFrameworkSdkInstallKeyValueV11,
159                 hasMSBuild: false),
160 
161             // v2.0
162             new DotNetFrameworkSpecLegacy(
163                 dotNetFrameworkVersion20,
164                 dotNetFrameworkRegistryKeyV20,
165                 dotNetFrameworkSetupRegistryInstalledName,
166                 dotNetFrameworkVersionFolderPrefixV20,
167                 dotNetFrameworkSdkInstallKeyValueV20,
168                 hasMSBuild: true),
169 
170             // v3.0
171             new DotNetFrameworkSpecV3(
172                 dotNetFrameworkVersion30,
173                 s_dotNetFrameworkRegistryKeyV30,
174                 "InstallSuccess",
175                 dotNetFrameworkVersionFolderPrefixV30,
176                 null,
177                 null,
178                 hasMSBuild: false),
179 
180             // v3.5
181             new DotNetFrameworkSpecV3(
182                 dotNetFrameworkVersion35,
183                 s_dotNetFrameworkRegistryKeyV35,
184                 dotNetFrameworkSetupRegistryInstalledName,
185                 dotNetFrameworkVersionFolderPrefixV35,
186                 "WinSDK-NetFx35Tools-x86",
187                 "InstallationFolder",
188                 hasMSBuild: true),
189 
190             // v4.0
191             CreateDotNetFrameworkSpecForV4(dotNetFrameworkVersion40, visualStudioVersion100),
192 
193             // v4.5
194             CreateDotNetFrameworkSpecForV4(dotNetFrameworkVersion45, visualStudioVersion110),
195 
196             // v4.5.1
197             CreateDotNetFrameworkSpecForV4(dotNetFrameworkVersion451, visualStudioVersion120),
198 
199             // v4.5.2
200             CreateDotNetFrameworkSpecForV4(dotNetFrameworkVersion452, visualStudioVersion120),
201 
202             // v4.6
203             CreateDotNetFrameworkSpecForV4(dotNetFrameworkVersion46, visualStudioVersion140),
204 
205             // v4.6.1
206             CreateDotNetFrameworkSpecForV4(dotNetFrameworkVersion461, visualStudioVersion140),
207 
208             // v4.6.2
209             CreateDotNetFrameworkSpecForV4(dotNetFrameworkVersion462, visualStudioVersion150),
210 
211             // v4.7
212             CreateDotNetFrameworkSpecForV4(dotNetFrameworkVersion47, visualStudioVersion150),
213 
214             // v4.7.1
215             CreateDotNetFrameworkSpecForV4(dotNetFrameworkVersion471, visualStudioVersion150),
216         };
217 
218         /// <summary>
219         /// List the supported visual studio versions.
220         /// </summary>
221         /// <remarks>
222         /// The items must be ordered by the version, because some methods depend on that fact to find the previous visual studio version.
223         /// </remarks>
224         private static readonly VisualStudioSpec[] s_visualStudioSpecs =
225         {
226             // VS10
227             new VisualStudioSpec(visualStudioVersion100, "Windows\\v7.0A", null, null, new []
228             {
229                 dotNetFrameworkVersion11,
230                 dotNetFrameworkVersion20,
231                 dotNetFrameworkVersion35,
232                 dotNetFrameworkVersion40,
233             }),
234 
235             // VS11
236             new VisualStudioSpec(visualStudioVersion110, "Windows\\v8.0A", "v8.0", "InstallationFolder", new []
237             {
238                 dotNetFrameworkVersion11,
239                 dotNetFrameworkVersion20,
240                 dotNetFrameworkVersion35,
241                 dotNetFrameworkVersion40,
242                 dotNetFrameworkVersion45,
243             }),
244 
245             // VS12
246             new VisualStudioSpec(visualStudioVersion120, "Windows\\v8.1A", "v8.1", "InstallationFolder", new []
247             {
248                 dotNetFrameworkVersion11,
249                 dotNetFrameworkVersion20,
250                 dotNetFrameworkVersion35,
251                 dotNetFrameworkVersion40,
252                 dotNetFrameworkVersion45,
253                 dotNetFrameworkVersion451,
254                 dotNetFrameworkVersion452
255             }),
256 
257             // VS14
258             new VisualStudioSpec(visualStudioVersion140, "NETFXSDK\\{0}", "v8.1", "InstallationFolder", new []
259             {
260                 dotNetFrameworkVersion11,
261                 dotNetFrameworkVersion20,
262                 dotNetFrameworkVersion35,
263                 dotNetFrameworkVersion40,
264                 dotNetFrameworkVersion45,
265                 dotNetFrameworkVersion451,
266                 dotNetFrameworkVersion452,
267                 dotNetFrameworkVersion46,
268                 dotNetFrameworkVersion461
269             }),
270 
271             // VS15
272             new VisualStudioSpec(visualStudioVersion150, "NETFXSDK\\{0}", "v8.1", "InstallationFolder", new []
273             {
274                 dotNetFrameworkVersion11,
275                 dotNetFrameworkVersion20,
276                 dotNetFrameworkVersion35,
277                 dotNetFrameworkVersion40,
278                 dotNetFrameworkVersion45,
279                 dotNetFrameworkVersion451,
280                 dotNetFrameworkVersion452,
281                 dotNetFrameworkVersion46,
282                 dotNetFrameworkVersion461,
283                 dotNetFrameworkVersion462,
284                 dotNetFrameworkVersion47,
285                 dotNetFrameworkVersion471
286             }),
287         };
288 
289         /// <summary>
290         /// Define explicit fallback rules for the request to get path of .net framework sdk tools folder.
291         /// The default rule is fallback to previous VS. However, there are some special cases that need
292         /// explicit rules, i.e. v4.5.1 on VS12 fallbacks to v4.5 on VS12.
293         /// </summary>
294         /// <remarks>
295         /// The rules are maintained in a 2-dimensions array. Each row defines a rule. The first column
296         /// defines the trigger condition. The second column defines the fallback .net and VS versions.
297         /// </remarks>
298         private static readonly Tuple<Version, Version>[,] s_explicitFallbackRulesForPathToDotNetFrameworkSdkTools =
299         {
300             // VS12
301             { Tuple.Create(dotNetFrameworkVersion451, visualStudioVersion120), Tuple.Create(dotNetFrameworkVersion45, visualStudioVersion120) },
302             { Tuple.Create(dotNetFrameworkVersion452, visualStudioVersion120), Tuple.Create(dotNetFrameworkVersion451, visualStudioVersion120) },
303 
304             // VS14
305             { Tuple.Create(dotNetFrameworkVersion451, visualStudioVersion140), Tuple.Create(dotNetFrameworkVersion45, visualStudioVersion140) },
306             { Tuple.Create(dotNetFrameworkVersion452, visualStudioVersion140), Tuple.Create(dotNetFrameworkVersion451, visualStudioVersion140) },
307             { Tuple.Create(dotNetFrameworkVersion46, visualStudioVersion140), Tuple.Create(dotNetFrameworkVersion451, visualStudioVersion140) },
308             { Tuple.Create(dotNetFrameworkVersion461, visualStudioVersion140), Tuple.Create(dotNetFrameworkVersion46, visualStudioVersion140) },
309 
310             // VS15
311             { Tuple.Create(dotNetFrameworkVersion451, visualStudioVersion150), Tuple.Create(dotNetFrameworkVersion45, visualStudioVersion150) },
312             { Tuple.Create(dotNetFrameworkVersion452, visualStudioVersion150), Tuple.Create(dotNetFrameworkVersion451, visualStudioVersion150) },
313             { Tuple.Create(dotNetFrameworkVersion46, visualStudioVersion150), Tuple.Create(dotNetFrameworkVersion451, visualStudioVersion150) },
314             { Tuple.Create(dotNetFrameworkVersion461, visualStudioVersion150), Tuple.Create(dotNetFrameworkVersion46, visualStudioVersion150) },
315             { Tuple.Create(dotNetFrameworkVersion462, visualStudioVersion150), Tuple.Create(dotNetFrameworkVersion461, visualStudioVersion150) },
316             { Tuple.Create(dotNetFrameworkVersion47, visualStudioVersion150), Tuple.Create(dotNetFrameworkVersion462, visualStudioVersion150) },
317             { Tuple.Create(dotNetFrameworkVersion471, visualStudioVersion150), Tuple.Create(dotNetFrameworkVersion47, visualStudioVersion150) },
318        };
319 
320         private static readonly IReadOnlyDictionary<Version, DotNetFrameworkSpec> s_dotNetFrameworkSpecDict;
321         private static readonly IReadOnlyDictionary<Version, VisualStudioSpec> s_visualStudioSpecDict;
322 
323         #endregion // Static member variables
324 
FrameworkLocationHelper()325         static FrameworkLocationHelper()
326         {
327             s_dotNetFrameworkSpecDict = s_dotNetFrameworkSpecs.ToDictionary(spec => spec.Version);
328             s_visualStudioSpecDict = s_visualStudioSpecs.ToDictionary(spec => spec.Version);
329         }
330 
331         #region Static properties
332 
333         internal static string PathToDotNetFrameworkV11
334         {
335             get
336             {
337                 return NativeMethodsShared.IsUnixLike
338                            ? Path.Combine(NativeMethodsShared.FrameworkBasePath, dotNetFrameworkVersionFolderPrefixV11)
339                            : GetPathToDotNetFrameworkV11(DotNetFrameworkArchitecture.Current);
340             }
341         }
342 
343         internal static string PathToDotNetFrameworkV20
344         {
345             get
346             {
347                 return NativeMethodsShared.IsUnixLike
348                            ? Path.Combine(NativeMethodsShared.FrameworkBasePath, dotNetFrameworkVersionFolderPrefixV20)
349                            : GetPathToDotNetFrameworkV20(DotNetFrameworkArchitecture.Current);
350             }
351         }
352 
353         internal static string PathToDotNetFrameworkV30
354         {
355             get
356             {
357                 return NativeMethodsShared.IsUnixLike
358                            ? Path.Combine(NativeMethodsShared.FrameworkBasePath, dotNetFrameworkVersionFolderPrefixV30)
359                            : GetPathToDotNetFrameworkV30(DotNetFrameworkArchitecture.Current);
360             }
361         }
362 
363         internal static string PathToDotNetFrameworkV35
364         {
365             get
366             {
367                 return NativeMethodsShared.IsUnixLike
368                            ? Path.Combine(NativeMethodsShared.FrameworkBasePath, dotNetFrameworkVersionFolderPrefixV35)
369                            : GetPathToDotNetFrameworkV35(DotNetFrameworkArchitecture.Current);
370             }
371         }
372 
373         internal static string PathToDotNetFrameworkV40
374         {
375             get
376             {
377                 return NativeMethodsShared.IsUnixLike
378                            ? Path.Combine(NativeMethodsShared.FrameworkBasePath, dotNetFrameworkVersionFolderPrefixV40)
379                            : GetPathToDotNetFrameworkV40(DotNetFrameworkArchitecture.Current);
380             }
381         }
382 
383         internal static string PathToDotNetFrameworkV45
384         {
385             get
386             {
387                 return NativeMethodsShared.IsUnixLike
388                            ? Path.Combine(NativeMethodsShared.FrameworkBasePath, dotNetFrameworkVersionFolderPrefixV45)
389                            : GetPathToDotNetFrameworkV45(DotNetFrameworkArchitecture.Current);
390             }
391         }
392 
393         internal static string PathToDotNetFrameworkSdkV11
394         {
395             get
396             {
397                 return NativeMethodsShared.IsUnixLike
398                            ? Path.Combine(NativeMethodsShared.FrameworkBasePath, dotNetFrameworkVersionFolderPrefixV11)
399                            : GetPathToDotNetFrameworkSdkTools(dotNetFrameworkVersion11, visualStudioVersionLatest);
400             }
401         }
402 
403         internal static string PathToDotNetFrameworkSdkV20
404         {
405             get
406             {
407                 return NativeMethodsShared.IsUnixLike
408                            ? Path.Combine(NativeMethodsShared.FrameworkBasePath, dotNetFrameworkVersionFolderPrefixV20)
409                            : GetPathToDotNetFrameworkSdkTools(dotNetFrameworkVersion20, visualStudioVersionLatest);
410             }
411         }
412 
413         /// <summary>
414         /// Because there is no longer a strong 1:1 mapping between FX versions and SDK
415         /// versions, if we're unable to locate the desired SDK version, we will try to
416         /// use whichever SDK version is installed by looking at the key pointing to the
417         /// "latest" version.
418         ///
419         /// This isn't ideal, but it will allow our tasks to function on any of several
420         /// related SDKs even if they don't have exactly the same versions.
421         ///
422         /// NOTE:  This returns the path to the root of the fallback SDK
423         /// </summary>
424         private static string FallbackDotNetFrameworkSdkInstallPath
425         {
426             get
427             {
428                 if (s_fallbackDotNetFrameworkSdkInstallPath == null)
429                 {
430                     // For non-Windows just get the current Framework path
431                     if (!NativeMethodsShared.IsWindows)
432                     {
433                         s_fallbackDotNetFrameworkSdkInstallPath = NativeMethodsShared.FrameworkCurrentPath;
434                     }
435 #if FEATURE_WIN32_REGISTRY
436                     else
437                     {
438                         s_fallbackDotNetFrameworkSdkInstallPath =
439                             FindRegistryValueUnderKey(
440                                 fallbackDotNetFrameworkSdkRegistryInstallPath,
441                                 fallbackDotNetFrameworkSdkInstallKeyValue);
442 
443                         if (EnvironmentUtilities.Is64BitProcess && s_fallbackDotNetFrameworkSdkInstallPath == null)
444                         {
445                             // Since we're 64-bit, what we just checked was the 64-bit fallback key -- so now let's
446                             // check the 32-bit one too, just in case.
447                             s_fallbackDotNetFrameworkSdkInstallPath =
448                                 FindRegistryValueUnderKey(
449                                     fallbackDotNetFrameworkSdkRegistryInstallPath,
450                                     fallbackDotNetFrameworkSdkInstallKeyValue,
451                                     RegistryView.Registry32);
452                         }
453                     }
454 #endif
455                 }
456 
457                 return s_fallbackDotNetFrameworkSdkInstallPath;
458             }
459         }
460 
461         /// <summary>
462         /// Because there is no longer a strong 1:1 mapping between FX versions and SDK
463         /// versions, if we're unable to locate the desired SDK version, we will try to
464         /// use whichever SDK version is installed by looking at the key pointing to the
465         /// "latest" version.
466         ///
467         /// This isn't ideal, but it will allow our tasks to function on any of several
468         /// related SDKs even if they don't have exactly the same versions.
469         ///
470         /// NOTE:  This explicitly returns the path to the 3.5 tools (bin) under the fallback
471         /// SDK, to match the data we're pulling from the registry now.
472         /// </summary>
473         private static string PathToV35ToolsInFallbackDotNetFrameworkSdk
474         {
475             get
476             {
477                 if (s_pathToV35ToolsInFallbackDotNetFrameworkSdk == null)
478                 {
479                     if (FallbackDotNetFrameworkSdkInstallPath != null)
480                     {
481                         if (NativeMethodsShared.IsWindows)
482                         {
483                             var endsWithASlash =
484                                 FallbackDotNetFrameworkSdkInstallPath.EndsWith(
485                                     Path.DirectorySeparatorChar.ToString(),
486                                     StringComparison.Ordinal);
487 
488                             s_pathToV35ToolsInFallbackDotNetFrameworkSdk =
489                                 Path.Combine(FallbackDotNetFrameworkSdkInstallPath, "bin");
490 
491                             // Path.Combine leaves no trailing slash, so if we had one before, be sure to add it back in
492                             if (endsWithASlash)
493                             {
494                                 s_pathToV35ToolsInFallbackDotNetFrameworkSdk = s_pathToV35ToolsInFallbackDotNetFrameworkSdk
495                                                                              + Path.DirectorySeparatorChar;
496                             }
497                         }
498                         else
499                         {
500                             s_pathToV35ToolsInFallbackDotNetFrameworkSdk = FallbackDotNetFrameworkSdkInstallPath;
501                         }
502                     }
503                 }
504 
505                 return s_pathToV35ToolsInFallbackDotNetFrameworkSdk;
506             }
507         }
508 
509         /// <summary>
510         /// Because there is no longer a strong 1:1 mapping between FX versions and SDK
511         /// versions, if we're unable to locate the desired SDK version, we will try to
512         /// use whichever SDK version is installed by looking at the key pointing to the
513         /// "latest" version.
514         ///
515         /// This isn't ideal, but it will allow our tasks to function on any of several
516         /// related SDKs even if they don't have exactly the same versions.
517         ///
518         /// NOTE:  This explicitly returns the path to the 4.X tools (bin\NetFX 4.0 Tools)
519         /// under the fallback SDK, to match the data we're pulling from the registry now.
520         /// </summary>
521         private static string PathToV4ToolsInFallbackDotNetFrameworkSdk
522         {
523             get
524             {
525                 if (s_pathToV4ToolsInFallbackDotNetFrameworkSdk == null)
526                 {
527                     if (FallbackDotNetFrameworkSdkInstallPath != null)
528                     {
529                         if (NativeMethodsShared.IsWindows)
530                         {
531                             bool endsWithASlash = FallbackDotNetFrameworkSdkInstallPath.EndsWith(
532                                 "\\",
533                                 StringComparison.Ordinal);
534 
535                             s_pathToV4ToolsInFallbackDotNetFrameworkSdk = Path.Combine(FallbackDotNetFrameworkSdkInstallPath, "bin", "NetFX 4.0 Tools");
536 
537                             // Path.Combine leaves no trailing slash, so if we had one before, be sure to add it back in
538                             if (endsWithASlash)
539                             {
540                                 s_pathToV4ToolsInFallbackDotNetFrameworkSdk = s_pathToV4ToolsInFallbackDotNetFrameworkSdk + "\\";
541                             }
542                         }
543                         else
544                         {
545                             s_pathToV4ToolsInFallbackDotNetFrameworkSdk = FallbackDotNetFrameworkSdkInstallPath;
546                         }
547                     }
548                 }
549 
550                 return s_pathToV4ToolsInFallbackDotNetFrameworkSdk;
551             }
552         }
553 
554         #endregion // Static properties
555 
556         #region Internal methods
557 
GetDotNetFrameworkSdkRootRegistryKey(Version dotNetFrameworkVersion, Version visualStudioVersion)558         internal static string GetDotNetFrameworkSdkRootRegistryKey(Version dotNetFrameworkVersion, Version visualStudioVersion)
559         {
560             RedirectVersionsIfNecessary(ref dotNetFrameworkVersion, ref visualStudioVersion);
561 
562             var dotNetFrameworkSpec = GetDotNetFrameworkSpec(dotNetFrameworkVersion);
563             var visualStudioSpec = GetVisualStudioSpec(visualStudioVersion);
564             ErrorUtilities.VerifyThrowArgument(visualStudioSpec.SupportedDotNetFrameworkVersions.Contains(dotNetFrameworkVersion), "FrameworkLocationHelper.UnsupportedFrameworkVersion", dotNetFrameworkVersion);
565             return dotNetFrameworkSpec.GetDotNetFrameworkSdkRootRegistryKey(visualStudioSpec);
566         }
567 
GetDotNetFrameworkSdkInstallKeyValue(Version dotNetFrameworkVersion, Version visualStudioVersion)568         internal static string GetDotNetFrameworkSdkInstallKeyValue(Version dotNetFrameworkVersion, Version visualStudioVersion)
569         {
570             RedirectVersionsIfNecessary(ref dotNetFrameworkVersion, ref visualStudioVersion);
571 
572             var dotNetFrameworkSpec = GetDotNetFrameworkSpec(dotNetFrameworkVersion);
573             var visualStudioSpec = GetVisualStudioSpec(visualStudioVersion);
574             ErrorUtilities.VerifyThrowArgument(visualStudioSpec.SupportedDotNetFrameworkVersions.Contains(dotNetFrameworkVersion), "FrameworkLocationHelper.UnsupportedFrameworkVersion", dotNetFrameworkVersion);
575             return dotNetFrameworkSpec.DotNetFrameworkSdkRegistryInstallationFolderName;
576         }
577 
GetDotNetFrameworkVersionFolderPrefix(Version dotNetFrameworkVersion)578         internal static string GetDotNetFrameworkVersionFolderPrefix(Version dotNetFrameworkVersion)
579         {
580             return GetDotNetFrameworkSpec(dotNetFrameworkVersion).DotNetFrameworkFolderPrefix;
581         }
582 
GetPathToWindowsSdk(Version dotNetFrameworkVersion)583         internal static string GetPathToWindowsSdk(Version dotNetFrameworkVersion)
584         {
585             return GetDotNetFrameworkSpec(dotNetFrameworkVersion).GetPathToWindowsSdk();
586         }
587 
GetPathToDotNetFrameworkReferenceAssemblies(Version dotNetFrameworkVersion)588         internal static string GetPathToDotNetFrameworkReferenceAssemblies(Version dotNetFrameworkVersion)
589         {
590             return GetDotNetFrameworkSpec(dotNetFrameworkVersion).GetPathToDotNetFrameworkReferenceAssemblies();
591         }
592 
GetPathToDotNetFrameworkSdkTools(Version dotNetFrameworkVersion, Version visualStudioVersion)593         internal static string GetPathToDotNetFrameworkSdkTools(Version dotNetFrameworkVersion, Version visualStudioVersion)
594         {
595             RedirectVersionsIfNecessary(ref dotNetFrameworkVersion, ref visualStudioVersion);
596 
597             var dotNetFrameworkSpec = GetDotNetFrameworkSpec(dotNetFrameworkVersion);
598             var visualStudioSpec = GetVisualStudioSpec(visualStudioVersion);
599             ErrorUtilities.VerifyThrowArgument(visualStudioSpec.SupportedDotNetFrameworkVersions.Contains(dotNetFrameworkVersion), "FrameworkLocationHelper.UnsupportedFrameworkVersion", dotNetFrameworkVersion);
600             return dotNetFrameworkSpec.GetPathToDotNetFrameworkSdkTools(visualStudioSpec);
601         }
602 
GetPathToDotNetFrameworkSdk(Version dotNetFrameworkVersion, Version visualStudioVersion)603         internal static string GetPathToDotNetFrameworkSdk(Version dotNetFrameworkVersion, Version visualStudioVersion)
604         {
605             RedirectVersionsIfNecessary(ref dotNetFrameworkVersion, ref visualStudioVersion);
606 
607             var dotNetFrameworkSpec = GetDotNetFrameworkSpec(dotNetFrameworkVersion);
608             var visualStudioSpec = GetVisualStudioSpec(visualStudioVersion);
609             ErrorUtilities.VerifyThrowArgument(visualStudioSpec.SupportedDotNetFrameworkVersions.Contains(dotNetFrameworkVersion), "FrameworkLocationHelper.UnsupportedFrameworkVersion", dotNetFrameworkVersion);
610             return dotNetFrameworkSpec.GetPathToDotNetFrameworkSdk(visualStudioSpec);
611         }
612 
GetPathToDotNetFrameworkV11(DotNetFrameworkArchitecture architecture)613         internal static string GetPathToDotNetFrameworkV11(DotNetFrameworkArchitecture architecture)
614         {
615             return GetPathToDotNetFramework(dotNetFrameworkVersion11, architecture);
616         }
617 
GetPathToDotNetFrameworkV20(DotNetFrameworkArchitecture architecture)618         internal static string GetPathToDotNetFrameworkV20(DotNetFrameworkArchitecture architecture)
619         {
620             return GetPathToDotNetFramework(dotNetFrameworkVersion20, architecture);
621         }
622 
GetPathToDotNetFrameworkV30(DotNetFrameworkArchitecture architecture)623         internal static string GetPathToDotNetFrameworkV30(DotNetFrameworkArchitecture architecture)
624         {
625             return GetPathToDotNetFramework(dotNetFrameworkVersion30, architecture);
626         }
627 
GetPathToDotNetFrameworkV35(DotNetFrameworkArchitecture architecture)628         internal static string GetPathToDotNetFrameworkV35(DotNetFrameworkArchitecture architecture)
629         {
630             return GetPathToDotNetFramework(dotNetFrameworkVersion35, architecture);
631         }
632 
GetPathToDotNetFrameworkV40(DotNetFrameworkArchitecture architecture)633         internal static string GetPathToDotNetFrameworkV40(DotNetFrameworkArchitecture architecture)
634         {
635             return GetPathToDotNetFramework(dotNetFrameworkVersion40, architecture);
636         }
637 
GetPathToDotNetFrameworkV45(DotNetFrameworkArchitecture architecture)638         internal static string GetPathToDotNetFrameworkV45(DotNetFrameworkArchitecture architecture)
639         {
640             return GetPathToDotNetFramework(dotNetFrameworkVersion45, architecture);
641         }
642 
GetPathToDotNetFramework(Version version)643         internal static string GetPathToDotNetFramework(Version version)
644         {
645             return GetPathToDotNetFramework(version, DotNetFrameworkArchitecture.Current);
646         }
647 
GetPathToDotNetFramework(Version version, DotNetFrameworkArchitecture architecture)648         internal static string GetPathToDotNetFramework(Version version, DotNetFrameworkArchitecture architecture)
649         {
650             return GetDotNetFrameworkSpec(version).GetPathToDotNetFramework(architecture);
651         }
652 
653 #if FEATURE_INSTALLED_MSBUILD
654         /// <summary>
655         /// Check the registry key and value to see if the .net Framework is installed on the machine.
656         /// </summary>
657         /// <param name="registryEntryToCheckInstall">Registry path to look for the value</param>
658         /// <param name="registryValueToCheckInstall">Key to retrieve the value from</param>
659         /// <returns>True if the registry key is 1 false if it is not there. This method also return true if the complus enviornment variables are set.</returns>
CheckForFrameworkInstallation(string registryEntryToCheckInstall, string registryValueToCheckInstall)660         private static bool CheckForFrameworkInstallation(string registryEntryToCheckInstall, string registryValueToCheckInstall)
661         {
662             // Get the complus install root and version
663             string complusInstallRoot = Environment.GetEnvironmentVariable("COMPLUS_INSTALLROOT");
664             string complusVersion = Environment.GetEnvironmentVariable("COMPLUS_VERSION");
665 
666             // Complus is not set we need to make sure the framework we are targeting is installed. Check the registry key before trying to find the directory.
667             // If complus is set then we will return that directory as the framework directory, there is no need to check the registry value for the framework and it may not even be installed.
668             if (String.IsNullOrEmpty(complusInstallRoot) && String.IsNullOrEmpty(complusVersion))
669             {
670                 if (NativeMethodsShared.IsWindows)
671                 {
672                     // If the registry entry is 1 then the framework is installed. Go ahead and find the directory. If it is not 1 then the framework is not installed, return null.
673                     return String.Compare("1", FindRegistryValueUnderKey(registryEntryToCheckInstall, registryValueToCheckInstall), StringComparison.OrdinalIgnoreCase) == 0;
674                 }
675                 // False for non-windows since there is nothing in the registry
676                 else
677                 {
678                     return false;
679                 }
680             }
681 
682             return true;
683         }
684 #endif
685 
686         /// <summary>
687         /// Heuristic that first considers the current runtime path and then searches the base of that path for the given
688         /// frameworks version.
689         /// </summary>
690         /// <param name="currentRuntimePath">The path to the runtime that is currently executing.</param>
691         /// <param name="prefix">Should be something like 'v1.2' that indicates the runtime version we want.</param>
692         /// <param name="directoryExists">Function that checks if directory exists.</param>
693         /// <param name="getDirectories">Delegate to method that can return filesystem entries.</param>
694         /// <param name="architecture">.NET framework architecture</param>
695         /// <returns>Will return 'null' if there is no target frameworks on this machine.</returns>
FindDotNetFrameworkPath( string currentRuntimePath, string prefix, DirectoryExists directoryExists, GetDirectories getDirectories, DotNetFrameworkArchitecture architecture )696         internal static string FindDotNetFrameworkPath
697         (
698             string currentRuntimePath,
699             string prefix,
700             DirectoryExists directoryExists,
701             GetDirectories getDirectories,
702             DotNetFrameworkArchitecture architecture
703         )
704         {
705             if (!NativeMethodsShared.IsWindows)
706             {
707                 if (!string.IsNullOrEmpty(prefix)
708                     && prefix.Substring(0, 1).Equals("v", StringComparison.OrdinalIgnoreCase))
709                 {
710                     prefix = prefix.Substring(1);
711                 }
712 
713                 var frameworkPath = Path.Combine(NativeMethodsShared.FrameworkBasePath, prefix ?? string.Empty);
714                 return directoryExists(frameworkPath) ? frameworkPath : null;
715             }
716 
717             // If the COMPLUS variables are set, they override everything -- that's the directory we want.
718             string complusInstallRoot = Environment.GetEnvironmentVariable("COMPLUS_INSTALLROOT");
719             string complusVersion = Environment.GetEnvironmentVariable("COMPLUS_VERSION");
720 
721             if (!String.IsNullOrEmpty(complusInstallRoot) && !String.IsNullOrEmpty(complusVersion))
722             {
723                 return Path.Combine(complusInstallRoot, complusVersion);
724             }
725 
726             // If the current runtime starts with correct prefix, then this is the runtime we want to use.
727             // However, only if we're requesting current architecture -- otherwise, the base path may be different, so we'll need to look it up.
728             string leaf = Path.GetFileName(currentRuntimePath);
729             if (leaf.StartsWith(prefix, StringComparison.Ordinal) && architecture == DotNetFrameworkArchitecture.Current)
730             {
731                 return currentRuntimePath;
732             }
733 
734             // We haven't managed to use exact methods to locate the FX, so
735             // search for the correct path with a heuristic.
736             string baseLocation = Path.GetDirectoryName(currentRuntimePath);
737             string searchPattern = prefix + "*";
738 
739             int indexOfFramework64 = baseLocation.IndexOf("Framework64", StringComparison.OrdinalIgnoreCase);
740 
741             if (indexOfFramework64 != -1 && architecture == DotNetFrameworkArchitecture.Bitness32)
742             {
743                 // need to get rid of just the 64, but want to look up 'Framework64' rather than '64' to avoid the case where
744                 // the path is something like 'C:\MyPath\64\Framework64'.  9 = length of 'Framework', to make the index match
745                 // the location of the '64'.
746                 int indexOf64 = indexOfFramework64 + 9;
747                 string tempLocation = baseLocation;
748                 baseLocation = tempLocation.Substring(0, indexOf64) + tempLocation.Substring(indexOf64 + 2, tempLocation.Length - indexOf64 - 2);
749             }
750             else if (indexOfFramework64 == -1 && architecture == DotNetFrameworkArchitecture.Bitness64)
751             {
752                 // need to add 64 -- since this is a heuristic, we assume that we just need to append.
753                 baseLocation = baseLocation + "64";
754             }
755             // we don't need to do anything if it's DotNetFrameworkArchitecture.Current.
756 
757             string[] directories;
758 
759             if (directoryExists(baseLocation))
760             {
761                 directories = getDirectories(baseLocation, searchPattern);
762             }
763             else
764             {
765                 // If we can't even find the base path, might as well give up now.
766                 return null;
767             }
768 
769             if (directories.Length == 0)
770             {
771                 // Couldn't find the path, return a null.
772                 return null;
773             }
774 
775             // We don't care which one we choose, but we want to be predictible.
776             // The intention here is to choose the alphabetical maximum.
777             string max = directories[0];
778 
779             // the max.EndsWith condition: pre beta 2 versions of v3.5 have build number like v3.5.20111.
780             // This was removed in beta2
781             // We should favor \v3.5 over \v3.5.xxxxx
782             // versions previous to 2.0 have .xxxx version numbers.  3.0 and 3.5 do not.
783             if (!max.EndsWith(prefix, StringComparison.OrdinalIgnoreCase))
784             {
785                 for (int i = 1; i < directories.Length; ++i)
786                 {
787                     if (directories[i].EndsWith(prefix, StringComparison.OrdinalIgnoreCase))
788                     {
789                         max = directories[i];
790                         break;
791                     }
792                     else if (String.Compare(directories[i], max, StringComparison.OrdinalIgnoreCase) > 0)
793                     {
794                         max = directories[i];
795                     }
796                 }
797             }
798 
799             return max;
800         }
801 
802         /// <summary>
803         /// Determine the 32 bit program files directory, this is used for finding where the reference assemblies live.
804         /// </summary>
GenerateProgramFiles32()805         internal static string GenerateProgramFiles32()
806         {
807             // With Mono, all we look for in Files32 should be found in the below location
808             if (!NativeMethodsShared.IsWindows && NativeMethodsShared.IsMono)
809             {
810                 return Path.Combine(NativeMethodsShared.FrameworkBasePath, "xbuild");
811             }
812 
813             // On a 64 bit machine we always want to use the program files x86.  If we are running as a 64 bit process then this variable will be set correctly
814             // If we are on a 32 bit machine or running as a 32 bit process then this variable will be null and the programFiles variable will be correct.
815 #if FEATURE_SPECIAL_FOLDERS
816             string programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
817 #else
818             string programFilesX86 = FileUtilities.GetFolderPath(FileUtilities.SpecialFolder.ProgramFilesX86);
819 #endif
820             if (String.IsNullOrEmpty(programFilesX86))
821             {
822                 // 32 bit box
823                 programFilesX86 = programFiles;
824             }
825 
826             return programFilesX86;
827         }
828 
829         /// <summary>
830         /// Determine the 64-bit program files directory, used as the basis for MSBuildExtensionsPath64.
831         /// Returns null if we're not on a 64-bit machine
832         /// </summary>
GenerateProgramFiles64()833         internal static string GenerateProgramFiles64()
834         {
835             // With Mono, all we look for in Files32 should be found in the below location
836             if (!NativeMethodsShared.IsWindows && NativeMethodsShared.IsMono)
837             {
838                 return Path.Combine(NativeMethodsShared.FrameworkBasePath, "xbuild");
839             }
840 
841             string programFilesX64 = null;
842             if (string.Equals(programFiles, programFiles32))
843             {
844                 // either we're in a 32-bit window, or we're on a 32-bit machine.
845                 // if we're on a 32-bit machine, ProgramW6432 won't exist
846                 // if we're on a 64-bit machine, ProgramW6432 will point to the correct Program Files.
847                 programFilesX64 = Environment.GetEnvironmentVariable("ProgramW6432");
848             }
849             else
850             {
851                 // 64-bit window on a 64-bit machine; %ProgramFiles% points to the 64-bit
852                 // Program Files already.
853                 programFilesX64 = programFiles;
854             }
855 
856             return programFilesX64;
857         }
858 
859         /// <summary>
860         /// Generate the path to the program files reference assembly location by taking in the program files special folder and then
861         /// using that path to generate the path to the reference assemblies location.
862         /// </summary>
GenerateProgramFilesReferenceAssemblyRoot()863         internal static string GenerateProgramFilesReferenceAssemblyRoot()
864         {
865             string combinedPath = Environment.GetEnvironmentVariable("ReferenceAssemblyRoot");
866             if (!String.IsNullOrEmpty(combinedPath))
867             {
868                 combinedPath = Path.GetFullPath(combinedPath);
869                 if (Directory.Exists(combinedPath))
870                 {
871                     return combinedPath;
872                 }
873             }
874 
875             combinedPath = NativeMethodsShared.IsWindows
876                                ? Path.Combine(programFiles32, "Reference Assemblies\\Microsoft\\Framework")
877                                : Path.Combine(NativeMethodsShared.FrameworkBasePath, "xbuild-frameworks");
878 
879             return Path.GetFullPath(combinedPath);
880         }
881 
882         /// <summary>
883         /// Given a ToolsVersion, find the path to the build tools folder for that ToolsVersion.
884         /// </summary>
885         /// <param name="toolsVersion">The ToolsVersion to look up</param>
886         /// <param name="architecture">Target build tools architecture.</param>
887         /// <returns>The path to the build tools folder for that ToolsVersion, if it exists, or
888         /// null otherwise</returns>
GeneratePathToBuildToolsForToolsVersion(string toolsVersion, DotNetFrameworkArchitecture architecture)889         internal static string GeneratePathToBuildToolsForToolsVersion(string toolsVersion, DotNetFrameworkArchitecture architecture)
890         {
891             if (string.Compare(toolsVersion, MSBuildConstants.CurrentToolsVersion, StringComparison.Ordinal) == 0)
892             {
893                 return GetPathToBuildToolsFromEnvironment(architecture);
894             }
895 
896             string toolsPath = null;
897 #if FEATURE_WIN32_REGISTRY
898             // If we're not looking for the current tools version, try the registry.
899             toolsPath = GetPathToBuildToolsFromRegistry(toolsVersion, architecture);
900 #endif
901 
902             // If all else fails, always use the current environment.
903             return toolsPath ?? GetPathToBuildToolsFromEnvironment(architecture);
904         }
905 
906         /// <summary>
907         /// Take the parts of the Target framework moniker and formulate the reference assembly path based on the the following pattern:
908         /// For a framework and version:
909         ///     $(TargetFrameworkRootPath)\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion)
910         /// For a subtype:
911         ///     $(TargetFrameworkRootPath)\$(TargetFrameworkIdentifier)\$(TargetFrameworkVersion)\SubType\$(TargetFrameworkSubType)
912         /// e.g.NET Framework v4.0 would locate its reference assemblies in:
913         ///     \Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0
914         /// e.g.Silverlight v2.0 would locate its reference assemblies in:
915         ///     \Program Files\Reference Assemblies\Microsoft\Framework\Silverlight\v2.0
916         /// e.g.NET Compact Framework v3.5, subtype PocketPC would locate its reference assemblies in:
917         ///     \Program Files\Reference Assemblies\Microsoft\Framework\.NETCompactFramework\v3.5\SubType\PocketPC
918         /// </summary>
919         /// <returns>The path to the reference assembly location</returns>
GenerateReferenceAssemblyPath(string targetFrameworkRootPath, FrameworkName frameworkName)920         internal static string GenerateReferenceAssemblyPath(string targetFrameworkRootPath, FrameworkName frameworkName)
921         {
922             ErrorUtilities.VerifyThrowArgumentNull(targetFrameworkRootPath, "targetFrameworkRootPath");
923             ErrorUtilities.VerifyThrowArgumentNull(frameworkName, "frameworkName");
924 
925             try
926             {
927                 string path = targetFrameworkRootPath;
928                 path = Path.Combine(path, frameworkName.Identifier);
929                 path = Path.Combine(path, "v" + frameworkName.Version.ToString());
930                 if (!String.IsNullOrEmpty(frameworkName.Profile))
931                 {
932                     path = Path.Combine(path, "Profile");
933                     path = Path.Combine(path, frameworkName.Profile);
934                 }
935 
936                 path = Path.GetFullPath(path);
937                 return path;
938             }
939             catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
940             {
941                 ErrorUtilities.ThrowInvalidOperation("FrameworkLocationHelper.CouldNotGenerateReferenceAssemblyDirectory", targetFrameworkRootPath, frameworkName.ToString(), e.Message);
942                 // The compiler does not see the massage above an as exception;
943                 return null;
944             }
945         }
946 
947         /// <summary>
948         /// Given a path, subtracts the requested number of directories and returns the result.
949         /// </summary>
950         /// <comments>
951         /// Internal only so that I can have the unit tests use it too, instead of duplicating the same code
952         /// </comments>
RemoveDirectories(string path, int numberOfLevelsToRemove)953         internal static string RemoveDirectories(string path, int numberOfLevelsToRemove)
954         {
955             ErrorUtilities.VerifyThrowArgumentOutOfRange(numberOfLevelsToRemove > 0, "what are you doing passing a negative number to this function??");
956 
957             string fixedPath = null;
958             if (path != null)
959             {
960                 // Record whether we had a slash or not so that we can tack it back on if necessary
961                 var endedWithASlash = path.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)
962                                       || path.EndsWith(
963                                           Path.AltDirectorySeparatorChar.ToString(),
964                                           StringComparison.Ordinal);
965 
966                 DirectoryInfo fixedPathInfo = new DirectoryInfo(path);
967                 for (int i = 0; i < numberOfLevelsToRemove; i++)
968                 {
969                     if (fixedPathInfo != null && fixedPathInfo.Parent != null)
970                     {
971                         fixedPathInfo = fixedPathInfo.Parent;
972                     }
973                 }
974 
975                 if (fixedPathInfo != null)
976                 {
977                     fixedPath = fixedPathInfo.FullName;
978                 }
979 
980                 if (fixedPath != null && endedWithASlash)
981                 {
982                     fixedPath = fixedPath + Path.DirectorySeparatorChar;
983                 }
984             }
985 
986             return fixedPath;
987         }
988 
989         /// <summary>
990         /// Look up the path to the build tools directory for the requested ToolsVersion in the .exe.config file of this executable
991         /// </summary>
GetPathToBuildToolsFromEnvironment(DotNetFrameworkArchitecture architecture)992         private static string GetPathToBuildToolsFromEnvironment(DotNetFrameworkArchitecture architecture)
993         {
994             switch (architecture)
995             {
996                 case DotNetFrameworkArchitecture.Bitness64:
997                     return BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64;
998                 case DotNetFrameworkArchitecture.Bitness32:
999                     return BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32;
1000                 default:
1001                     return BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory;
1002             }
1003         }
1004 
GetTargetFrameworkRootFallbackPaths(string toolsVersion)1005         internal static IList<string> GetTargetFrameworkRootFallbackPaths(string toolsVersion)
1006         {
1007 #if FEATURE_SYSTEM_CONFIGURATION
1008             return GetTargetFrameworkRootFallbackPathsFromConfigFor(toolsVersion);
1009 #else
1010             return new List<string>();
1011 #endif
1012         }
1013 
1014 #if FEATURE_SYSTEM_CONFIGURATION
1015         /// <summary>
1016         /// Returns the list of fallback search paths for looking up Target frameworks for the current OS,
1017         /// specified in app.config like:
1018         ///
1019         ///
1020         /// </summary>
GetTargetFrameworkRootFallbackPathsFromConfigFor(string toolsVersion)1021         internal static IList<string> GetTargetFrameworkRootFallbackPathsFromConfigFor(string toolsVersion)
1022         {
1023             try
1024             {
1025                 ToolsetElement toolset = GetToolsetElementFromConfigFor(toolsVersion);
1026                 PropertyElement searchPathsfromConfiguration = toolset?.PropertyElements.GetElement("TargetFrameworkRootPathSearchPaths" + GetOSNameForTargetFrameworkRoot());
1027                 var searchPaths = searchPathsfromConfiguration?.Value;
1028 
1029                 if (searchPaths != null)
1030                 {
1031                     //FIXME: Split on unix
1032                     var pathsList = searchPaths.Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries);
1033                     return pathsList;
1034                 }
1035             }
1036             catch (ConfigurationException)
1037             {
1038                 // may happen if the .exe.config contains bad data.  Shouldn't ever happen in
1039                 // practice since we'll long since have loaded all toolsets in the toolset loading
1040                 // code and thrown errors to the user at that point if anything was invalid, but just
1041                 // in case, just eat the exception here, so that we can go on to look in the registry
1042                 // to see if there is any valid data there.
1043             }
1044             return new List<string>();
1045         }
1046 #endif
1047 
1048 #if FEATURE_SYSTEM_CONFIGURATION
GetToolsetElementFromConfigFor(string toolsVersion)1049         private static ToolsetElement GetToolsetElementFromConfigFor(string toolsVersion)
1050         {
1051             ToolsetConfigurationSection configurationSection = null;
1052 
1053             if (ToolsetConfigurationReaderHelpers.ConfigurationFileMayHaveToolsets())
1054             {
1055                 Configuration configuration = ConfigurationManager.OpenExeConfiguration(
1056                                                 BuildEnvironmentHelper.Instance.CurrentMSBuildExePath);
1057 
1058                 configurationSection = ToolsetConfigurationReaderHelpers.ReadToolsetConfigurationSection(configuration);
1059             }
1060 
1061             return configurationSection?.Toolsets.GetElement(toolsVersion);
1062         }
1063 #endif
1064 
1065         /// <summary>
1066         /// OS name that can be used as the suffix for `TargetFrameworkRootPathSearchPaths` property name
1067         /// in app.config
1068         /// </summary>
GetOSNameForTargetFrameworkRoot()1069         private static string GetOSNameForTargetFrameworkRoot()
1070         {
1071             if (NativeMethodsShared.IsWindows)
1072             {
1073                 return "Windows";
1074             }
1075 
1076             if (NativeMethodsShared.IsOSX)
1077             {
1078                 return "OSX";
1079             }
1080 
1081             return "Unix";
1082         }
1083 
1084 #if FEATURE_WIN32_REGISTRY
1085         /// <summary>
1086         /// Look up the path to the build tools directory in the registry for the requested ToolsVersion and requested architecture
1087         /// </summary>
GetPathToBuildToolsFromRegistry(string toolsVersion, DotNetFrameworkArchitecture architecture)1088         private static string GetPathToBuildToolsFromRegistry(string toolsVersion, DotNetFrameworkArchitecture architecture)
1089         {
1090             string toolsVersionSpecificKey = ToolsVersionsRegistryPath + "\\" + toolsVersion;
1091 
1092             RegistryView view = RegistryView.Default;
1093 
1094             switch (architecture)
1095             {
1096                 case DotNetFrameworkArchitecture.Bitness32:
1097                     view = RegistryView.Registry32;
1098                     break;
1099                 case DotNetFrameworkArchitecture.Bitness64:
1100                     view = RegistryView.Registry64;
1101                     break;
1102                 case DotNetFrameworkArchitecture.Current:
1103                     view = RegistryView.Default;
1104                     break;
1105             }
1106 
1107             string toolsPath = FindRegistryValueUnderKey(toolsVersionSpecificKey, MSBuildConstants.ToolsPath, view);
1108             return toolsPath;
1109         }
1110 #endif
1111 
1112         #endregion // Internal methods
1113 
1114         #region Private methods
1115 
1116         /// <summary>
1117         /// Will return the path to the dot net framework reference assemblies if they exist under the program files\reference assembies\microsoft\framework directory
1118         /// or null if the directory does not exist.
1119         /// </summary>
GenerateReferenceAssemblyDirectory(string versionPrefix)1120         private static string GenerateReferenceAssemblyDirectory(string versionPrefix)
1121         {
1122             string programFilesReferenceAssemblyDirectory = Path.Combine(programFilesReferenceAssemblyLocation, versionPrefix);
1123             string referenceAssemblyDirectory = null;
1124 
1125             if (Directory.Exists(programFilesReferenceAssemblyDirectory))
1126             {
1127                 referenceAssemblyDirectory = programFilesReferenceAssemblyDirectory;
1128             }
1129 
1130             return referenceAssemblyDirectory;
1131         }
1132 
1133 #if FEATURE_WIN32_REGISTRY
1134         /// <summary>
1135         /// Look for the given registry value under the given key.
1136         /// </summary>
FindRegistryValueUnderKey( string registryBaseKeyName, string registryKeyName, RegistryView registryView = RegistryView.Default)1137         private static string FindRegistryValueUnderKey
1138         (
1139             string registryBaseKeyName,
1140             string registryKeyName,
1141             RegistryView registryView = RegistryView.Default)
1142         {
1143             using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView))
1144             using (RegistryKey subKey = baseKey.OpenSubKey(registryBaseKeyName))
1145             {
1146                 return subKey?.GetValue(registryKeyName)?.ToString();
1147             }
1148         }
1149 #endif
1150 
GetVisualStudioSpec(Version version)1151         private static VisualStudioSpec GetVisualStudioSpec(Version version)
1152         {
1153             ErrorUtilities.VerifyThrowArgument(s_visualStudioSpecDict.ContainsKey(version), "FrameworkLocationHelper.UnsupportedVisualStudioVersion", version);
1154             return s_visualStudioSpecDict[version];
1155         }
1156 
GetDotNetFrameworkSpec(Version version)1157         private static DotNetFrameworkSpec GetDotNetFrameworkSpec(Version version)
1158         {
1159             ErrorUtilities.VerifyThrowArgument(s_dotNetFrameworkSpecDict.ContainsKey(version), "FrameworkLocationHelper.UnsupportedFrameworkVersion", version);
1160             return s_dotNetFrameworkSpecDict[version];
1161         }
1162 
1163         /// <summary>
1164         /// Helper method to create an instance of <see cref="DotNetFrameworkSpec"/> for .net v4.x,
1165         /// because most of attributes are the same for v4.x versions.
1166         /// </summary>
1167         /// <param name="version">.net framework version.</param>
1168         /// <param name="visualStudioVersion">Version of Visual Studio</param>
1169         /// <returns></returns>
CreateDotNetFrameworkSpecForV4(Version version, Version visualStudioVersion)1170         private static DotNetFrameworkSpec CreateDotNetFrameworkSpecForV4(Version version, Version visualStudioVersion)
1171         {
1172             return new DotNetFrameworkSpec(
1173                 version,
1174                 dotNetFrameworkRegistryKey: dotNetFrameworkSetupRegistryPath + "\\v4\\Full",
1175                 dotNetFrameworkSetupRegistryInstalledName: "Install",
1176                 dotNetFrameworkVersionFolderPrefix: NativeMethodsShared.IsWindows ? "v4.0" : "v4.5",
1177                 dotNetFrameworkSdkRegistryToolsKey: "WinSDK-NetFx40Tools-x86",
1178                 dotNetFrameworkSdkRegistryInstallationFolderName: "InstallationFolder",
1179                 hasMSBuild: true,
1180                 visualStudioVersion: visualStudioVersion);
1181         }
1182 
RedirectVersionsIfNecessary(ref Version dotNetFrameworkVersion, ref Version visualStudioVersion)1183         private static void RedirectVersionsIfNecessary(ref Version dotNetFrameworkVersion, ref Version visualStudioVersion)
1184         {
1185             if (dotNetFrameworkVersion == dotNetFrameworkVersion45 && visualStudioVersion == visualStudioVersion100)
1186             {
1187                 // There is no VS10 equivalent -- so just return the VS11 version
1188                 visualStudioVersion = visualStudioVersion110;
1189                 return;
1190             }
1191 
1192             if (dotNetFrameworkVersion == dotNetFrameworkVersion35 && visualStudioVersion > visualStudioVersion110)
1193             {
1194                 // Fall back to Dev11 location -- 3.5 tools MSI was reshipped unchanged, so there
1195                 // essentially are no 12-specific 3.5 tools.
1196                 visualStudioVersion = visualStudioVersion110;
1197                 return;
1198             }
1199         }
1200 
1201 #endregion
1202 
1203         private class VisualStudioSpec
1204         {
1205             /// <summary>
1206             /// The key in registry to indicate the corresponding .net framework in this visual studio.
1207             /// i.e. 'v8.0A' for VS11.
1208             /// </summary>
1209             private readonly string _dotNetFrameworkSdkRegistryKey;
1210 
VisualStudioSpec( Version version, string dotNetFrameworkSdkRegistryKey, string windowsSdkRegistryKey, string windowsSdkRegistryInstallationFolderName, Version[] supportedDotNetFrameworkVersions)1211             public VisualStudioSpec(
1212                 Version version,
1213                 string dotNetFrameworkSdkRegistryKey,
1214                 string windowsSdkRegistryKey,
1215                 string windowsSdkRegistryInstallationFolderName,
1216                 Version[] supportedDotNetFrameworkVersions)
1217             {
1218                 Version = version;
1219                 _dotNetFrameworkSdkRegistryKey = dotNetFrameworkSdkRegistryKey;
1220                 WindowsSdkRegistryKey = windowsSdkRegistryKey;
1221                 WindowsSdkRegistryInstallationFolderName = windowsSdkRegistryInstallationFolderName;
1222                 SupportedDotNetFrameworkVersions = supportedDotNetFrameworkVersions;
1223             }
1224 
1225             /// <summary>
1226             /// The version of this visual studio.
1227             /// </summary>
1228             public Version Version { get; }
1229 
1230             /// <summary>
1231             /// The list of supported .net framework versions in this visual studio.
1232             /// </summary>
1233             public Version[] SupportedDotNetFrameworkVersions { get; }
1234 
1235             /// <summary>
1236             /// The key in registry to indicate the corresponding windows sdk, i.e. "v8.0" for VS11.
1237             /// </summary>
1238             public string WindowsSdkRegistryKey { get; }
1239 
1240             /// <summary>
1241             /// The name in registry to indicate the sdk installation folder path, i.e. "InstallationFolder" for windows v8.0.
1242             /// </summary>
1243             public string WindowsSdkRegistryInstallationFolderName { get; }
1244 
1245             /// <summary>
1246             /// The key in the registry to indicate the corresponding .net framework in this visual studio.
1247             /// i.e. 'v8.0A' for VS11.
1248             /// </summary>
GetDotNetFrameworkSdkRegistryKey(Version dotNetSdkVersion)1249             public string GetDotNetFrameworkSdkRegistryKey(Version dotNetSdkVersion)
1250             {
1251                 string sdkVersionFolder = "4.6"; // Default for back-compat
1252 
1253                 if (dotNetSdkVersion == dotNetFrameworkVersion471)
1254                 {
1255                     sdkVersionFolder = "4.7.1";
1256                 }
1257                 if (dotNetSdkVersion == dotNetFrameworkVersion47)
1258                 {
1259                     sdkVersionFolder = "4.7";
1260                 }
1261                 else if (dotNetSdkVersion == dotNetFrameworkVersion461)
1262                 {
1263                     sdkVersionFolder = "4.6.1";
1264                 }
1265                 else if (dotNetSdkVersion == dotNetFrameworkVersion462)
1266                 {
1267                     sdkVersionFolder = "4.6.2";
1268                 }
1269 
1270                 // If the path is formatted to include a version number if we need to include that.
1271                 // (e.g. NETFXSDK\{0} should be NETFXSDK\4.6 or NETFXSDK\4.6.1)
1272                 // Note: before VS2015 this key was the same per instance of VS and didn't need to change.
1273                 // In that case the string will not contain a format item and will not be modified.
1274                 return string.Format(_dotNetFrameworkSdkRegistryKey, sdkVersionFolder);
1275             }
1276         }
1277 
1278         private class DotNetFrameworkSpec
1279         {
1280             private const string HKLM = "HKEY_LOCAL_MACHINE";
1281             private const string MicrosoftSDKsRegistryKey = @"SOFTWARE\Microsoft\Microsoft SDKs";
1282 
1283             /// <summary>
1284             /// The registry key of this .net framework, i.e. "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" for .net v4.5.
1285             /// </summary>
1286             private readonly string _dotNetFrameworkRegistryKey;
1287 
1288             /// <summary>
1289             /// The name in registry to indicate that this .net framework is installed, i.e. "Install" for .net v4.5.
1290             /// </summary>
1291             private readonly string _dotNetFrameworkSetupRegistryInstalledName;
1292 
1293             /// <summary>
1294             /// The key in registry to indicate the sdk tools folder, i.e. "WinSDK-NetFx40Tools-x86" for .net v4.5.
1295             /// </summary>
1296             private readonly string _dotNetFrameworkSdkRegistryToolsKey;
1297 
1298             /// <summary>
1299             /// The version of visual studio that shipped with this .net framework.
1300             /// </summary>
1301             private readonly Version _visualStudioVersion;
1302 
1303             /// <summary>
1304             /// Does this .net framework include MSBuild?
1305             /// </summary>
1306             private readonly bool _hasMsBuild;
1307 
1308             /// <summary>
1309             /// Cached paths of .net framework on different architecture.
1310             /// </summary>
1311             private readonly ConcurrentDictionary<DotNetFrameworkArchitecture, string> _pathsToDotNetFramework;
1312 
1313             /// <summary>
1314             /// Cached paths of .net framework sdk tools folder path on different visual studio version.
1315             /// </summary>
1316             private readonly ConcurrentDictionary<Version, string> _pathsToDotNetFrameworkSdkTools;
1317 
1318 #if FEATURE_WIN32_REGISTRY
1319             /// <summary>
1320             /// Cached path of the corresponding windows sdk.
1321             /// </summary>
1322             private string _pathToWindowsSdk;
1323 #endif
1324 
1325             /// <summary>
1326             /// Cached path of .net framework reference assemblies.
1327             /// </summary>
1328             protected string _pathToDotNetFrameworkReferenceAssemblies;
1329 
DotNetFrameworkSpec( Version version, string dotNetFrameworkRegistryKey, string dotNetFrameworkSetupRegistryInstalledName, string dotNetFrameworkVersionFolderPrefix, string dotNetFrameworkSdkRegistryToolsKey, string dotNetFrameworkSdkRegistryInstallationFolderName, bool hasMSBuild = true, Version visualStudioVersion = null)1330             public DotNetFrameworkSpec(
1331                 Version version,
1332                 string dotNetFrameworkRegistryKey,
1333                 string dotNetFrameworkSetupRegistryInstalledName,
1334                 string dotNetFrameworkVersionFolderPrefix,
1335                 string dotNetFrameworkSdkRegistryToolsKey,
1336                 string dotNetFrameworkSdkRegistryInstallationFolderName,
1337                 bool hasMSBuild = true,
1338                 Version visualStudioVersion = null)
1339             {
1340                 this.Version = version;
1341                 this._visualStudioVersion = visualStudioVersion;
1342                 this._dotNetFrameworkRegistryKey = dotNetFrameworkRegistryKey;
1343                 this._dotNetFrameworkSetupRegistryInstalledName = dotNetFrameworkSetupRegistryInstalledName;
1344                 this.DotNetFrameworkFolderPrefix = dotNetFrameworkVersionFolderPrefix;
1345                 this._dotNetFrameworkSdkRegistryToolsKey = dotNetFrameworkSdkRegistryToolsKey;
1346                 this.DotNetFrameworkSdkRegistryInstallationFolderName = dotNetFrameworkSdkRegistryInstallationFolderName;
1347                 this._hasMsBuild = hasMSBuild;
1348                 this._pathsToDotNetFramework = new ConcurrentDictionary<DotNetFrameworkArchitecture, string>();
1349                 this._pathsToDotNetFrameworkSdkTools = new ConcurrentDictionary<Version, string>();
1350             }
1351 
1352             /// <summary>
1353             /// The version of this .net framework.
1354             /// </summary>
1355             public Version Version { get; }
1356 
1357             /// <summary>
1358             /// The name in registry to indicate the sdk installation folder path, i.e. "InstallationFolder" for .net v4.5.
1359             /// </summary>
1360             public string DotNetFrameworkSdkRegistryInstallationFolderName { get; }
1361 
1362             /// <summary>
1363             /// Folder prefix, i.e. v4.0 for .net v4.5.
1364             /// </summary>
1365             public string DotNetFrameworkFolderPrefix { get; }
1366 
1367             /// <summary>
1368             /// Get the FrameworkName for this version of the .NET Framework.
1369             /// </summary>
1370             private FrameworkName FrameworkName => new FrameworkName(dotNetFrameworkIdentifier, this.Version);
1371 
1372             /// <summary>
1373             /// Gets the full registry key of this .net framework Sdk for the given visual studio version.
1374             /// i.e. "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0A\WinSDK-NetFx40Tools-x86" for .net v4.5 on VS11.
1375             /// </summary>
GetDotNetFrameworkSdkRootRegistryKey(VisualStudioSpec visualStudioSpec)1376             public virtual string GetDotNetFrameworkSdkRootRegistryKey(VisualStudioSpec visualStudioSpec)
1377             {
1378                 return string.Join(@"\", HKLM, MicrosoftSDKsRegistryKey, visualStudioSpec.GetDotNetFrameworkSdkRegistryKey(Version), _dotNetFrameworkSdkRegistryToolsKey);
1379             }
1380 
1381             /// <summary>
1382             /// Gets the full path of .net framework for the given architecture.
1383             /// </summary>
GetPathToDotNetFramework(DotNetFrameworkArchitecture architecture)1384             public virtual string GetPathToDotNetFramework(DotNetFrameworkArchitecture architecture)
1385             {
1386 #if !FEATURE_INSTALLED_MSBUILD
1387                 return null;
1388 #else
1389                 string cachedPath;
1390                 if (this._pathsToDotNetFramework.TryGetValue(architecture, out cachedPath))
1391                 {
1392                     return cachedPath;
1393                 }
1394 
1395                 // Otherwise, check to see if we're even installed.  If not, return null -- no point in setting the static
1396                 // variables to null when that's what they are already.
1397                 if (NativeMethodsShared.IsWindows)
1398                 {
1399                     if (!CheckForFrameworkInstallation(
1400                             this._dotNetFrameworkRegistryKey,
1401                             this._dotNetFrameworkSetupRegistryInstalledName))
1402                     {
1403                         return null;
1404                     }
1405                 }
1406 
1407                 // We're installed and we haven't found this framework path yet -- so find it!
1408                 string generatedPathToDotNetFramework =
1409                                 FindDotNetFrameworkPath(
1410                                     Path.GetDirectoryName(typeof(object).Module.FullyQualifiedName),
1411                                     this.DotNetFrameworkFolderPrefix,
1412                                     Directory.Exists,
1413                                     Directory.GetDirectories,
1414                                     architecture);
1415 
1416                 // .net was improperly uninstalled: msbuild.exe isn't there
1417                 if (this._hasMsBuild &&
1418                     generatedPathToDotNetFramework != null &&
1419                     !File.Exists(Path.Combine(generatedPathToDotNetFramework, NativeMethodsShared.IsWindows ? "MSBuild.exe" : "mcs.exe")))
1420                 {
1421                     return null;
1422                 }
1423 
1424                 if (!string.IsNullOrEmpty(generatedPathToDotNetFramework))
1425                 {
1426                     _pathsToDotNetFramework[architecture] = generatedPathToDotNetFramework;
1427                 }
1428 
1429                 return generatedPathToDotNetFramework;
1430 #endif
1431             }
1432 
1433             /// <summary>
1434             /// Gets the full path of .net framework sdk tools for the given visual studio version.
1435             /// i.e. "C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\" for .net v4.5 on VS11.
1436             /// </summary>
GetPathToDotNetFrameworkSdkTools(VisualStudioSpec visualStudioSpec)1437             public virtual string GetPathToDotNetFrameworkSdkTools(VisualStudioSpec visualStudioSpec)
1438             {
1439                 string cachedPath;
1440                 if (this._pathsToDotNetFrameworkSdkTools.TryGetValue(visualStudioSpec.Version, out cachedPath))
1441                 {
1442                     return cachedPath;
1443                 }
1444 
1445                 string generatedPathToDotNetFrameworkSdkTools = null;
1446 
1447                 if (NativeMethodsShared.IsUnixLike)
1448                 {
1449                     string frameworkPath = NativeMethodsShared.FrameworkBasePath;
1450                     if (!string.IsNullOrEmpty(frameworkPath))
1451                     {
1452                         frameworkPath = Path.Combine(frameworkPath, this.Version.ToString());
1453                     }
1454 
1455                     if (!string.IsNullOrEmpty(frameworkPath) && Directory.Exists(frameworkPath))
1456                     {
1457                         generatedPathToDotNetFrameworkSdkTools = frameworkPath;
1458                     }
1459                 }
1460 #if FEATURE_WIN32_REGISTRY
1461                 else
1462                 {
1463                     string registryPath = string.Join(
1464                                               @"\",
1465                                               MicrosoftSDKsRegistryKey,
1466                                               visualStudioSpec.GetDotNetFrameworkSdkRegistryKey(Version),
1467                                               this._dotNetFrameworkSdkRegistryToolsKey);
1468 
1469                     // For the Dev10 SDK, we check the registry that corresponds to the current process' bitness, rather than
1470                     // always the 32-bit one the way we do for Dev11 and onward, since that's what we did in Dev10 as well.
1471                     // As of Dev11, the SDK reg keys are installed in the 32-bit registry.
1472                     RegistryView registryView = visualStudioSpec.Version == visualStudioVersion100 ? RegistryView.Default : RegistryView.Registry32;
1473 
1474                     generatedPathToDotNetFrameworkSdkTools = FindRegistryValueUnderKey(
1475                         registryPath,
1476                         this.DotNetFrameworkSdkRegistryInstallationFolderName,
1477                         registryView);
1478 
1479                     if (string.IsNullOrEmpty(generatedPathToDotNetFrameworkSdkTools))
1480                     {
1481                         // Fallback mechanisms.
1482 
1483                         // Try to find explicit fallback rule.
1484                         // i.e. v4.5.1 on VS12 fallbacks to v4.5 on VS12.
1485                         bool foundExplicitRule = false;
1486                         for (int i = 0; i < s_explicitFallbackRulesForPathToDotNetFrameworkSdkTools.GetLength(0); ++i)
1487                         {
1488                             var trigger = s_explicitFallbackRulesForPathToDotNetFrameworkSdkTools[i, 0];
1489                             if (trigger.Item1 == this.Version && trigger.Item2 == visualStudioSpec.Version)
1490                             {
1491                                 foundExplicitRule = true;
1492                                 var fallback = s_explicitFallbackRulesForPathToDotNetFrameworkSdkTools[i, 1];
1493                                 generatedPathToDotNetFrameworkSdkTools = FallbackToPathToDotNetFrameworkSdkToolsInPreviousVersion(
1494                                     fallback.Item1,
1495                                     fallback.Item2);
1496                                 break;
1497                             }
1498                         }
1499 
1500                         // Otherwise, fallback to previous VS.
1501                         // i.e. fallback to v110 if the current visual studio version is v120.
1502                         if (!foundExplicitRule)
1503                         {
1504                             int index = Array.IndexOf(s_visualStudioSpecs, visualStudioSpec);
1505                             if (index > 0)
1506                             {
1507                                 // The items in the array "visualStudioSpecs" must be ordered by version. That would allow us to fallback to the previous visual studio version easily.
1508                                 VisualStudioSpec fallbackVisualStudioSpec = s_visualStudioSpecs[index - 1];
1509                                 generatedPathToDotNetFrameworkSdkTools = FallbackToPathToDotNetFrameworkSdkToolsInPreviousVersion(
1510                                     this.Version,
1511                                     fallbackVisualStudioSpec.Version);
1512                             }
1513                         }
1514                     }
1515                 }
1516 #endif
1517                 if (string.IsNullOrEmpty(generatedPathToDotNetFrameworkSdkTools))
1518                 {
1519                     // Fallback to "default" ultimately.
1520                     generatedPathToDotNetFrameworkSdkTools = FallbackToDefaultPathToDotNetFrameworkSdkTools(this.Version);
1521                 }
1522 
1523                 if (!string.IsNullOrEmpty(generatedPathToDotNetFrameworkSdkTools))
1524                 {
1525                     this._pathsToDotNetFrameworkSdkTools[visualStudioSpec.Version] = generatedPathToDotNetFrameworkSdkTools;
1526                 }
1527 
1528                 return generatedPathToDotNetFrameworkSdkTools;
1529             }
1530 
1531             /// <summary>
1532             /// Gets the full path of .net framework sdk.
1533             /// i.e. "C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\" for .net v4.5 on VS11.
1534             /// </summary>
GetPathToDotNetFrameworkSdk(VisualStudioSpec visualStudioSpec)1535             public virtual string GetPathToDotNetFrameworkSdk(VisualStudioSpec visualStudioSpec)
1536             {
1537                 string pathToBinRoot = this.GetPathToDotNetFrameworkSdkTools(visualStudioSpec);
1538                 if (NativeMethodsShared.IsWindows)
1539                 {
1540                     pathToBinRoot = RemoveDirectories(pathToBinRoot, 2);
1541                 }
1542 
1543                 return pathToBinRoot;
1544             }
1545 
1546             /// <summary>
1547             /// Gets the full path of reference assemblies folder.
1548             /// i.e. "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\" for .net v4.5.
1549             /// </summary>
GetPathToDotNetFrameworkReferenceAssemblies()1550             public virtual string GetPathToDotNetFrameworkReferenceAssemblies()
1551             {
1552                 if (this._pathToDotNetFrameworkReferenceAssemblies == null)
1553                 {
1554                     // when a user requests the 40 reference assembly path we don't need to read the redist list because we will not be chaining so we may as well just
1555                     // generate the path and save us some time.
1556                     string referencePath = GenerateReferenceAssemblyPath(FrameworkLocationHelper.programFilesReferenceAssemblyLocation, this.FrameworkName);
1557                     if (Directory.Exists(referencePath))
1558                     {
1559                         this._pathToDotNetFrameworkReferenceAssemblies = FileUtilities.EnsureTrailingSlash(referencePath);
1560                     }
1561                 }
1562 
1563                 return this._pathToDotNetFrameworkReferenceAssemblies;
1564             }
1565 
1566             /// <summary>
1567             /// Gets the full path of the corresponding windows sdk shipped with this .net framework.
1568             /// i.e. "C:\Program Files (x86)\Windows Kits\8.0\" for v8.0 (shipped with .net v4.5 and VS11).
1569             /// </summary>
GetPathToWindowsSdk()1570             public virtual string GetPathToWindowsSdk()
1571             {
1572 #if FEATURE_WIN32_REGISTRY
1573                 if (this._pathToWindowsSdk == null)
1574                 {
1575                     ErrorUtilities.VerifyThrowArgument(this._visualStudioVersion != null, "FrameworkLocationHelper.UnsupportedFrameworkVersionForWindowsSdk", this.Version);
1576 
1577                     var visualStudioSpec = GetVisualStudioSpec(this._visualStudioVersion);
1578 
1579                     if (string.IsNullOrEmpty(visualStudioSpec.WindowsSdkRegistryKey) || string.IsNullOrEmpty(visualStudioSpec.WindowsSdkRegistryInstallationFolderName))
1580                     {
1581                         ErrorUtilities.ThrowArgument("FrameworkLocationHelper.UnsupportedFrameworkVersionForWindowsSdk", this.Version);
1582                     }
1583 
1584                     string registryPath = string.Join(@"\", MicrosoftSDKsRegistryKey, "Windows", visualStudioSpec.WindowsSdkRegistryKey);
1585 
1586                     // As of Dev11, the SDK reg keys are installed in the 32-bit registry.
1587                     this._pathToWindowsSdk = FindRegistryValueUnderKey(
1588                         registryPath,
1589                         visualStudioSpec.WindowsSdkRegistryInstallationFolderName,
1590                         RegistryView.Registry32);
1591                 }
1592                 return this._pathToWindowsSdk;
1593 #else
1594                 return null;
1595 #endif
1596             }
1597 
FallbackToPathToDotNetFrameworkSdkToolsInPreviousVersion(Version dotNetFrameworkVersion, Version visualStudioVersion)1598             private static string FallbackToPathToDotNetFrameworkSdkToolsInPreviousVersion(Version dotNetFrameworkVersion, Version visualStudioVersion)
1599             {
1600                 VisualStudioSpec visualStudioSpec;
1601                 DotNetFrameworkSpec dotNetFrameworkSpec;
1602                 if (s_visualStudioSpecDict.TryGetValue(visualStudioVersion, out visualStudioSpec)
1603                     && s_dotNetFrameworkSpecDict.TryGetValue(dotNetFrameworkVersion, out dotNetFrameworkSpec)
1604                     && visualStudioSpec.SupportedDotNetFrameworkVersions.Contains(dotNetFrameworkVersion))
1605                 {
1606                     return dotNetFrameworkSpec.GetPathToDotNetFrameworkSdkTools(visualStudioSpec);
1607                 }
1608 
1609                 return null;
1610             }
1611 
FallbackToDefaultPathToDotNetFrameworkSdkTools(Version dotNetFrameworkVersion)1612             private static string FallbackToDefaultPathToDotNetFrameworkSdkTools(Version dotNetFrameworkVersion)
1613             {
1614                 if (dotNetFrameworkVersion.Major == 4)
1615                 {
1616                     return PathToV4ToolsInFallbackDotNetFrameworkSdk;
1617                 }
1618 
1619                 if (dotNetFrameworkVersion == dotNetFrameworkVersion35)
1620                 {
1621                     return PathToV35ToolsInFallbackDotNetFrameworkSdk;
1622                 }
1623 
1624                 return null;
1625             }
1626         }
1627 
1628         /// <summary>
1629         /// Specialized implementation for legacy .net framework v1.1 and v2.0.
1630         /// </summary>
1631         private class DotNetFrameworkSpecLegacy : DotNetFrameworkSpec
1632         {
1633 #if FEATURE_WIN32_REGISTRY
1634             private string _pathToDotNetFrameworkSdkTools;
1635 #endif
1636 
DotNetFrameworkSpecLegacy( Version version, string dotNetFrameworkRegistryKey, string dotNetFrameworkSetupRegistryInstalledName, string dotNetFrameworkVersionFolderPrefix, string dotNetFrameworkSdkRegistryInstallationFolderName, bool hasMSBuild)1637             public DotNetFrameworkSpecLegacy(
1638                 Version version,
1639                 string dotNetFrameworkRegistryKey,
1640                 string dotNetFrameworkSetupRegistryInstalledName,
1641                 string dotNetFrameworkVersionFolderPrefix,
1642                 string dotNetFrameworkSdkRegistryInstallationFolderName,
1643                 bool hasMSBuild)
1644                 : base(version,
1645                       dotNetFrameworkRegistryKey,
1646                       dotNetFrameworkSetupRegistryInstalledName,
1647                       dotNetFrameworkVersionFolderPrefix,
1648                       null,
1649                       dotNetFrameworkSdkRegistryInstallationFolderName,
1650                       hasMSBuild)
1651             {
1652             }
1653 
1654             /// <summary>
1655             /// Gets the full registry key of this .net framework Sdk for the given visual studio version.
1656             /// i.e. "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework" for v1.1 and v2.0.
1657             /// </summary>
GetDotNetFrameworkSdkRootRegistryKey(VisualStudioSpec visualStudioSpec)1658             public override string GetDotNetFrameworkSdkRootRegistryKey(VisualStudioSpec visualStudioSpec)
1659             {
1660                 return fullDotNetFrameworkRegistryKey;
1661             }
1662 
1663             /// <summary>
1664             /// Gets the full path of .net framework sdk tools for the given visual studio version.
1665             /// </summary>
GetPathToDotNetFrameworkSdkTools(VisualStudioSpec visualStudioSpec)1666             public override string GetPathToDotNetFrameworkSdkTools(VisualStudioSpec visualStudioSpec)
1667             {
1668 #if FEATURE_WIN32_REGISTRY
1669                 if (_pathToDotNetFrameworkSdkTools == null)
1670                 {
1671                     _pathToDotNetFrameworkSdkTools = FindRegistryValueUnderKey(
1672                         dotNetFrameworkRegistryPath,
1673                         this.DotNetFrameworkSdkRegistryInstallationFolderName);
1674                 }
1675 
1676                 return _pathToDotNetFrameworkSdkTools;
1677 #else
1678                 return null;
1679 #endif
1680             }
1681 
1682             /// <summary>
1683             /// Gets the full path of .net framework sdk, which is the full path of .net framework sdk tools for v1.1 and v2.0.
1684             /// </summary>
GetPathToDotNetFrameworkSdk(VisualStudioSpec visualStudioSpec)1685             public override string GetPathToDotNetFrameworkSdk(VisualStudioSpec visualStudioSpec)
1686             {
1687                 return this.GetPathToDotNetFrameworkSdkTools(visualStudioSpec);
1688             }
1689 
1690             /// <summary>
1691             /// Gets the full path of reference assemblies folder, which is the full path of .net framework for v1.1 and v2.0.
1692             /// </summary>
GetPathToDotNetFrameworkReferenceAssemblies()1693             public override string GetPathToDotNetFrameworkReferenceAssemblies()
1694             {
1695                 return this.GetPathToDotNetFramework(DotNetFrameworkArchitecture.Current);
1696             }
1697         }
1698 
1699         /// <summary>
1700         /// Specialized implementation for legacy .net framework v3.0 and v3.5.
1701         /// </summary>
1702         private class DotNetFrameworkSpecV3 : DotNetFrameworkSpec
1703         {
DotNetFrameworkSpecV3( Version version, string dotNetFrameworkRegistryKey, string dotNetFrameworkSetupRegistryInstalledName, string dotNetFrameworkVersionFolderPrefix, string dotNetFrameworkSdkRegistryToolsKey, string dotNetFrameworkSdkRegistryInstallationFolderName, bool hasMSBuild)1704             public DotNetFrameworkSpecV3(
1705                 Version version,
1706                 string dotNetFrameworkRegistryKey,
1707                 string dotNetFrameworkSetupRegistryInstalledName,
1708                 string dotNetFrameworkVersionFolderPrefix,
1709                 string dotNetFrameworkSdkRegistryToolsKey,
1710                 string dotNetFrameworkSdkRegistryInstallationFolderName,
1711                 bool hasMSBuild)
1712                 : base(version,
1713                       dotNetFrameworkRegistryKey,
1714                       dotNetFrameworkSetupRegistryInstalledName,
1715                       dotNetFrameworkVersionFolderPrefix,
1716                       dotNetFrameworkSdkRegistryToolsKey,
1717                       dotNetFrameworkSdkRegistryInstallationFolderName,
1718                       hasMSBuild)
1719             {
1720             }
1721 
1722             /// <summary>
1723             /// Gets the full path of .net framework sdk.
1724             /// i.e. "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\" for .net v3.5 on VS11.
1725             /// </summary>
GetPathToDotNetFrameworkSdk(VisualStudioSpec visualStudioSpec)1726             public override string GetPathToDotNetFrameworkSdk(VisualStudioSpec visualStudioSpec)
1727             {
1728                 string pathToBinRoot = this.GetPathToDotNetFrameworkSdkTools(visualStudioSpec);
1729                 pathToBinRoot = RemoveDirectories(pathToBinRoot, 1);
1730                 return pathToBinRoot;
1731             }
1732 
1733             /// <summary>
1734             /// Gets the full path of reference assemblies folder.
1735             /// i.e. "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.5\" for v3.5.
1736             /// </summary>
GetPathToDotNetFrameworkReferenceAssemblies()1737             public override string GetPathToDotNetFrameworkReferenceAssemblies()
1738             {
1739                 if (this._pathToDotNetFrameworkReferenceAssemblies== null)
1740                 {
1741 #if FEATURE_WIN32_REGISTRY
1742                     this._pathToDotNetFrameworkReferenceAssemblies = FindRegistryValueUnderKey(
1743                         dotNetFrameworkAssemblyFoldersRegistryPath + "\\" + this.DotNetFrameworkFolderPrefix,
1744                         referenceAssembliesRegistryValueName);
1745 #endif
1746 
1747                     if (this._pathToDotNetFrameworkReferenceAssemblies == null)
1748                     {
1749                         this._pathToDotNetFrameworkReferenceAssemblies = GenerateReferenceAssemblyDirectory(this.DotNetFrameworkFolderPrefix);
1750                     }
1751                 }
1752 
1753                 return this._pathToDotNetFrameworkReferenceAssemblies;
1754             }
1755         }
1756     }
1757 }
1758