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