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 // Object which reads toolset information from the registry.
5 //-----------------------------------------------------------------------
6 #if FEATURE_WIN32_REGISTRY
7 
8 using Microsoft.Win32;
9 using System;
10 using System.Collections.Generic;
11 using System.IO;
12 using System.Security;
13 
14 using Microsoft.Build.Shared;
15 using error = Microsoft.Build.Shared.ErrorUtilities;
16 using RegistryKeyWrapper = Microsoft.Build.Internal.RegistryKeyWrapper;
17 using RegistryException = Microsoft.Build.Exceptions.RegistryException;
18 using InvalidToolsetDefinitionException = Microsoft.Build.Exceptions.InvalidToolsetDefinitionException;
19 using Constants = Microsoft.Build.Internal.Constants;
20 using Microsoft.Build.Construction;
21 using Microsoft.Build.Collections;
22 using Microsoft.Build.Execution;
23 using Microsoft.Build.Internal;
24 
25 namespace Microsoft.Build.Evaluation
26 {
27     /// <summary>
28     /// Reads registry at the base key and returns a Dictionary keyed on ToolsVersion.
29     /// Dictionary contains another dictionary of (property name, property value) pairs.
30     /// If a registry value is not a string, this will throw a InvalidToolsetDefinitionException.
31     /// An example of how the registry will look (note that the DefaultToolsVersion is per-MSBuild-version)
32     /// [HKLM]\SOFTWARE\Microsoft
33     ///   msbuild
34     ///     3.5
35     ///       @DefaultToolsVersion = 2.0
36     ///     ToolsVersions
37     ///       2.0
38     ///         @MSBuildToolsPath = D:\SomeFolder
39     ///       3.5
40     ///         @MSBuildToolsPath = D:\SomeOtherFolder
41     ///         @MSBuildBinPath = D:\SomeOtherFolder
42     ///         @SomePropertyName = PropertyOtherValue
43     /// </summary>
44     internal class ToolsetRegistryReader : ToolsetReader
45     {
46         /// <summary>
47         /// Registry location for storing tools version dependent data for msbuild
48         /// </summary>
49         private const string MSBuildRegistryPath = @"SOFTWARE\Microsoft\MSBuild";
50 
51         /// <summary>
52         /// Cached registry wrapper at root of the msbuild entries
53         /// </summary>
54         private RegistryKeyWrapper _msbuildRegistryWrapper;
55 
56         /// <summary>
57         /// Default constructor
58         /// </summary>
ToolsetRegistryReader(PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties)59         internal ToolsetRegistryReader(PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties)
60             : this(environmentProperties, globalProperties, new RegistryKeyWrapper(MSBuildRegistryPath))
61         {
62         }
63 
64         /// <summary>
65         /// Constructor overload accepting a registry wrapper for unit testing purposes only
66         /// </summary>
ToolsetRegistryReader(PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties, RegistryKeyWrapper msbuildRegistryWrapper)67         internal ToolsetRegistryReader(PropertyDictionary<ProjectPropertyInstance> environmentProperties, PropertyDictionary<ProjectPropertyInstance> globalProperties, RegistryKeyWrapper msbuildRegistryWrapper)
68             : base(environmentProperties, globalProperties)
69         {
70             error.VerifyThrowArgumentNull(msbuildRegistryWrapper, "msbuildRegistryWrapper");
71 
72             _msbuildRegistryWrapper = msbuildRegistryWrapper;
73         }
74 
75         /// <summary>
76         /// Returns the list of tools versions
77         /// </summary>
78         protected override IEnumerable<ToolsetPropertyDefinition> ToolsVersions
79         {
80             get
81             {
82                 string[] toolsVersionNames = Array.Empty<string>();
83                 try
84                 {
85                     RegistryKeyWrapper subKey = null;
86                     using (subKey = _msbuildRegistryWrapper.OpenSubKey("ToolsVersions"))
87                     {
88                         toolsVersionNames = subKey.GetSubKeyNames();
89                     }
90                 }
91                 catch (RegistryException ex)
92                 {
93                     InvalidToolsetDefinitionException.Throw(ex, "RegistryReadError", ex.Source, ex.Message);
94                 }
95 
96                 foreach (string toolsVersionName in toolsVersionNames)
97                 {
98                     // For the purposes of error location, use the registry path instead of a file name
99                     IElementLocation location = new RegistryLocation(_msbuildRegistryWrapper.Name + "\\ToolsVersions\\" + toolsVersionName);
100 
101                     yield return new ToolsetPropertyDefinition(toolsVersionName, string.Empty, location);
102                 }
103             }
104         }
105 
106         /// <summary>
107         /// Returns the default tools version, or null if none was specified
108         /// </summary>
109         protected override string DefaultToolsVersion
110         {
111             get
112             {
113                 string defaultToolsVersion = null;
114 
115                 // We expect to find the DefaultToolsVersion value under a registry key named for our
116                 // version, e.g., "3.5"
117                 using (RegistryKeyWrapper defaultToolsVersionKey = _msbuildRegistryWrapper.OpenSubKey(Constants.AssemblyVersion))
118                 {
119                     if (defaultToolsVersionKey != null)
120                     {
121                         defaultToolsVersion = GetValue(defaultToolsVersionKey, "DefaultToolsVersion");
122                     }
123                 }
124 
125                 return defaultToolsVersion;
126             }
127         }
128 
129         /// <summary>
130         /// Returns the path to find override tasks, or null if none was specified
131         /// </summary>
132         protected override string MSBuildOverrideTasksPath
133         {
134             get
135             {
136                 string defaultToolsVersion = null;
137 
138                 // We expect to find the MsBuildOverrideTasksPath value under a registry key named for our
139                 // version, e.g., "4.0"
140                 using (RegistryKeyWrapper defaultToolsVersionKey = _msbuildRegistryWrapper.OpenSubKey(Constants.AssemblyVersion))
141                 {
142                     if (defaultToolsVersionKey != null)
143                     {
144                         defaultToolsVersion = GetValue(defaultToolsVersionKey, ReservedPropertyNames.overrideTasksPath);
145                     }
146                 }
147 
148                 return defaultToolsVersion;
149             }
150         }
151 
152         /// <summary>
153         /// ToolsVersion to use as the default ToolsVersion for this version of MSBuild
154         /// </summary>
155         protected override string DefaultOverrideToolsVersion
156         {
157             get
158             {
159                 string defaultOverrideToolsVersion = null;
160 
161                 // We expect to find the MsBuildOverrideTasksPath value under a registry key named for our
162                 // version, e.g., "12.0"
163                 using (RegistryKeyWrapper defaultOverrideToolsVersionKey = _msbuildRegistryWrapper.OpenSubKey(Constants.AssemblyVersion))
164                 {
165                     if (defaultOverrideToolsVersionKey != null)
166                     {
167                         defaultOverrideToolsVersion = GetValue(defaultOverrideToolsVersionKey, ReservedPropertyNames.defaultOverrideToolsVersion);
168                     }
169                 }
170 
171                 return defaultOverrideToolsVersion;
172             }
173         }
174 
175         /// <summary>
176         /// Provides an enumerator over property definitions for a specified tools version
177         /// </summary>
178         /// <param name="toolsVersion">The tools version</param>
179         /// <returns>An enumeration of property definitions</returns>
GetPropertyDefinitions(string toolsVersion)180         protected override IEnumerable<ToolsetPropertyDefinition> GetPropertyDefinitions(string toolsVersion)
181         {
182             RegistryKeyWrapper toolsVersionWrapper = null;
183             try
184             {
185                 try
186                 {
187                     toolsVersionWrapper = _msbuildRegistryWrapper.OpenSubKey("ToolsVersions\\" + toolsVersion);
188                 }
189                 catch (RegistryException ex)
190                 {
191                     InvalidToolsetDefinitionException.Throw(ex, "RegistryReadError", ex.Source, ex.Message);
192                 }
193 
194                 foreach (string propertyName in toolsVersionWrapper.GetValueNames())
195                 {
196                     yield return CreatePropertyFromRegistry(toolsVersionWrapper, propertyName);
197                 }
198             }
199             finally
200             {
201                 if (toolsVersionWrapper != null)
202                 {
203                     toolsVersionWrapper.Dispose();
204                 }
205             }
206         }
207 
208         /// <summary>
209         /// Provides an enumerator over the set of sub-toolset names available to a particular
210         /// toolsversion
211         /// </summary>
212         /// <param name="toolsVersion">The tools version.</param>
213         /// <returns>An enumeration of the sub-toolsets that belong to that toolsversion.</returns>
GetSubToolsetVersions(string toolsVersion)214         protected override IEnumerable<string> GetSubToolsetVersions(string toolsVersion)
215         {
216             RegistryKeyWrapper toolsVersionWrapper = null;
217             try
218             {
219                 try
220                 {
221                     toolsVersionWrapper = _msbuildRegistryWrapper.OpenSubKey("ToolsVersions\\" + toolsVersion);
222                 }
223                 catch (RegistryException ex)
224                 {
225                     InvalidToolsetDefinitionException.Throw(ex, "RegistryReadError", ex.Source, ex.Message);
226                 }
227 
228                 return toolsVersionWrapper.GetSubKeyNames();
229             }
230             finally
231             {
232                 if (toolsVersionWrapper != null)
233                 {
234                     toolsVersionWrapper.Dispose();
235                 }
236             }
237         }
238 
239         /// <summary>
240         /// Provides an enumerator over property definitions for a specified sub-toolset version
241         /// under a specified toolset version.
242         /// </summary>
243         /// <param name="toolsVersion">The tools version.</param>
244         /// <param name="subToolsetVersion">The sub-toolset version.</param>
245         /// <returns>An enumeration of property definitions.</returns>
GetSubToolsetPropertyDefinitions(string toolsVersion, string subToolsetVersion)246         protected override IEnumerable<ToolsetPropertyDefinition> GetSubToolsetPropertyDefinitions(string toolsVersion, string subToolsetVersion)
247         {
248             ErrorUtilities.VerifyThrowArgumentLength(subToolsetVersion, "subToolsetVersion");
249 
250             RegistryKeyWrapper toolsVersionWrapper = null;
251             RegistryKeyWrapper subToolsetWrapper = null;
252 
253             try
254             {
255                 try
256                 {
257                     toolsVersionWrapper = _msbuildRegistryWrapper.OpenSubKey("ToolsVersions\\" + toolsVersion);
258                 }
259                 catch (RegistryException ex)
260                 {
261                     InvalidToolsetDefinitionException.Throw(ex, "RegistryReadError", ex.Source, ex.Message);
262                 }
263 
264                 try
265                 {
266                     subToolsetWrapper = toolsVersionWrapper.OpenSubKey(subToolsetVersion);
267                 }
268                 catch (RegistryException ex)
269                 {
270                     InvalidToolsetDefinitionException.Throw(ex, "RegistryReadError", ex.Source, ex.Message);
271                 }
272 
273                 foreach (string propertyName in subToolsetWrapper.GetValueNames())
274                 {
275                     yield return CreatePropertyFromRegistry(subToolsetWrapper, propertyName);
276                 }
277             }
278             finally
279             {
280                 if (toolsVersionWrapper != null)
281                 {
282                     toolsVersionWrapper.Dispose();
283                 }
284 
285                 if (subToolsetWrapper != null)
286                 {
287                     subToolsetWrapper.Dispose();
288                 }
289             }
290         }
291 
292         /// <summary>
293         /// Returns a map of MSBuildExtensionsPath* property names/kind to list of search paths
294         /// </summary>
GetProjectImportSearchPathsTable(string toolsVersion, string os)295         protected override Dictionary<string, ProjectImportPathMatch> GetProjectImportSearchPathsTable(string toolsVersion, string os)
296         {
297             return new Dictionary<string, ProjectImportPathMatch>();
298         }
299 
300         /// <summary>
301         /// Given a registry location containing a property name and value, create the ToolsetPropertyDefinition that maps to it
302         /// </summary>
303         /// <param name="toolsetWrapper">Wrapper for the key that we're getting values from</param>
304         /// <param name="propertyName">The name of the property whose value we wish to generate a ToolsetPropertyDefinition for.</param>
305         /// <returns>A ToolsetPropertyDefinition instance corresponding to the property name requested.</returns>
CreatePropertyFromRegistry(RegistryKeyWrapper toolsetWrapper, string propertyName)306         private static ToolsetPropertyDefinition CreatePropertyFromRegistry(RegistryKeyWrapper toolsetWrapper, string propertyName)
307         {
308             string propertyValue = null;
309 
310             if (propertyName != null && propertyName.Length == 0)
311             {
312                 InvalidToolsetDefinitionException.Throw("PropertyNameInRegistryHasZeroLength", toolsetWrapper.Name);
313             }
314 
315             try
316             {
317                 propertyValue = GetValue(toolsetWrapper, propertyName);
318             }
319             catch (RegistryException ex)
320             {
321                 InvalidToolsetDefinitionException.Throw(ex, "RegistryReadError", ex.Source, ex.Message);
322             }
323 
324             // For the purposes of error location, use the registry path instead of a file name
325             IElementLocation location = new RegistryLocation(toolsetWrapper.Name + "@" + propertyName);
326 
327             return new ToolsetPropertyDefinition(propertyName, propertyValue, location);
328         }
329 
330         /// <summary>
331         /// Reads a string value from the specified registry key
332         /// </summary>
333         /// <param name="wrapper">wrapper around key</param>
334         /// <param name="valueName">name of the value</param>
335         /// <returns>string data in the value</returns>
GetValue(RegistryKeyWrapper wrapper, string valueName)336         private static string GetValue(RegistryKeyWrapper wrapper, string valueName)
337         {
338             if (wrapper.Exists())
339             {
340                 object result = wrapper.GetValue(valueName);
341 
342                 // RegistryKey.GetValue returns null if the value is not present
343                 // and String.Empty if the value is present and no data is defined.
344                 // We preserve this distinction, because a string property in the registry with
345                 // no value really has an empty string for a value (which is a valid property value)
346                 // rather than null for a value (which is an invalid property value)
347                 if (result != null)
348                 {
349                     // Must be a value of string type
350                     if (!(result is string))
351                     {
352                         InvalidToolsetDefinitionException.Throw("NonStringDataInRegistry", wrapper.Name + "@" + valueName);
353                     }
354 
355                     return result.ToString();
356                 }
357             }
358 
359             return null;
360         }
361     }
362 }
363 #endif