/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System.Collections; using System.Globalization; using System.IO; using System.Reflection; using System.Resources; using System.Security; using Microsoft.Win32; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// /// This class is responsible for loading resources using the PSSnapin dll and /// associated registry entries. /// /// /// /// The class creates an app-domain to load the resource assemblies in. Upon dispose /// the class unloads the app-domain to ensure the assemblies get unloaded. It uses /// ReflectionOnlyLoad and ReflectionOnlyLoadFrom to ensure that no code can execute /// and that dependencies are not loaded. This allows us to load assemblies that were /// built with different version of the CLR. /// /// internal sealed class RegistryStringResourceIndirect : IDisposable { /// /// Creates an instance of the RegistryStringResourceIndirect class. /// /// /// /// A new instance of the RegistryStringResourceIndirect class. /// /// internal static RegistryStringResourceIndirect GetResourceIndirectReader() { return new RegistryStringResourceIndirect(); } #region IDisposable Members /// /// Set to true when object is disposed /// /// private bool _disposed; /// /// Dispose method unloads the app domain that was /// created in the constructor. /// /// public void Dispose() { if (_disposed == false) { if (_domain != null) { AppDomain.Unload(_domain); _domain = null; _resourceRetriever = null; } } _disposed = true; } #endregion IDisposable Members /// /// The app-domain in which the resources will be loaded. /// /// private AppDomain _domain; /// /// The class that is created in the app-domain which does the resource loading. /// /// private ResourceRetriever _resourceRetriever; /// /// Creates the app-domain and the instance of the ResourceRetriever and /// sets the private fields with the references. /// /// private void CreateAppDomain() { if (_domain == null) { // Create an app-domain to load the resource assemblies in so that they can be // unloaded. _domain = AppDomain.CreateDomain("ResourceIndirectDomain"); _resourceRetriever = (ResourceRetriever)_domain.CreateInstanceAndUnwrap( Assembly.GetExecutingAssembly().FullName, "System.Management.Automation.ResourceRetriever"); } } /// /// Retrieves a resource string based on a resource reference stored in the specified /// registry key. /// /// /// /// The key in which there is a value that contains the reference to the resource /// to retrieve. /// /// /// /// The name of the value in the registry key that contains the reference to the resource. /// /// /// /// The full name of the assembly from which to load the resource. /// /// /// /// The full path of the assembly from which to load the resource. /// /// /// /// The resource string that was loaded or null if it could not be found. /// /// /// /// This method ensures that an appropriate registry entry exists and that it contains /// a properly formatted resource reference ("BaseName,ResourceID"). It then creates an /// app-domain (or uses and existing one if it already exists on the instance of the class) /// and an instance of the ResourceRetriever in that app-domain. It then calls the ResourceRetriever /// to load the specified assembly and retrieve the resource. The assembly is loaded using ReflectionOnlyLoad /// or ReflectionOnlyLoadFrom using the assemblyName or moduleName (respectively) so that /// no code can be executed. /// /// The app-domain is unloaded when this class instance is disposed. /// /// internal string GetResourceStringIndirect( RegistryKey key, string valueName, string assemblyName, string modulePath) { if (_disposed) { throw PSTraceSource.NewInvalidOperationException(MshSnapinInfo.ResourceReaderDisposed); } if (key == null) { throw PSTraceSource.NewArgumentNullException("key"); } if (String.IsNullOrEmpty(valueName)) { throw PSTraceSource.NewArgumentException("valueName"); } if (String.IsNullOrEmpty(assemblyName)) { throw PSTraceSource.NewArgumentException("assemblyName"); } if (String.IsNullOrEmpty(modulePath)) { throw PSTraceSource.NewArgumentException("modulePath"); } string result = null; do // false loop { // Read the resource reference from the registry string regValue = GetRegKeyValueAsString(key, valueName); if (regValue == null) { break; } result = GetResourceStringIndirect(assemblyName, modulePath, regValue); } while (false); return result; } /// /// Retrieves a resource string based on a resource reference supplied in . /// /// /// /// The full name of the assembly from which to load the resource. /// /// /// /// The full path of the assembly from which to load the resource. /// /// /// /// A comma separated basename and resource id pair. /// /// /// /// The resource string that was loaded or null if it could not be found. /// /// /// /// This method ensures that is a properly formatted /// resource reference ("BaseName,ResourceID"). It then creates an app-domain (or uses /// an existing one if it already exists on the instance of the class) and an instance /// of the ResourceRetriever in that app-domain. It then calls the ResourceRetriever /// to load the specified assembly and retrieve the resource. The assembly is loaded using ReflectionOnlyLoad /// or ReflectionOnlyLoadFrom using the assemblyName or moduleName (respectively) so that /// no code can be executed. /// /// The app-domain is unloaded when this class instance is disposed. /// /// internal string GetResourceStringIndirect( string assemblyName, string modulePath, string baseNameRIDPair) { if (_disposed) { throw PSTraceSource.NewInvalidOperationException(MshSnapinInfo.ResourceReaderDisposed); } if (String.IsNullOrEmpty(assemblyName)) { throw PSTraceSource.NewArgumentException("assemblyName"); } if (String.IsNullOrEmpty(modulePath)) { throw PSTraceSource.NewArgumentException("modulePath"); } if (String.IsNullOrEmpty(baseNameRIDPair)) { throw PSTraceSource.NewArgumentException("baseNameRIDPair"); } string result = null; do // false loop { // Initialize the app-domain and resource reader if not already initialized if (_resourceRetriever == null) { CreateAppDomain(); } // If the app-domain failed to load or the ResourceRetriever instance wasn't // created, then return null. if (_resourceRetriever == null) { break; } // Parse the resource reference string[] resourceSplit = baseNameRIDPair.Split(Utils.Separators.Comma); if (resourceSplit.Length != 2) { break; } string baseName = resourceSplit[0]; string resourceID = resourceSplit[1]; // Get the resource in the app-domain result = _resourceRetriever.GetStringResource(assemblyName, modulePath, baseName, resourceID); } while (false); return result; } /// /// Retrieves a string value from the registry /// /// /// /// The key to retrieve the value from. /// /// /// /// The name of the value to retrieve. /// /// /// /// The string value of the registry key value. /// /// private static string GetRegKeyValueAsString(RegistryKey key, string valueName) { string result = null; try { // Check the type of the value RegistryValueKind kind = key.GetValueKind(valueName); if (kind == RegistryValueKind.String) { // Get the value since it is a string result = key.GetValue(valueName) as string; } } catch (ArgumentException) { } catch (IOException) { } catch (SecurityException) { } return result; } } /// /// This class is the worker class used by RegistryStringResourceIndirect to load the resource /// assemblies and retrieve the resources inside the alternate app-domain. /// /// internal class ResourceRetriever : MarshalByRefObject { /// /// Loads the specified assembly in the app-domain and retrieves the specified resource string. /// /// /// /// Full name of the assembly to retrieve the resource from. /// /// /// /// Full path of the assembly to retrieve the resource from. /// /// /// /// The resource base name to retrieve. /// /// /// /// The resource ID of the resource to retrieve. /// /// /// /// The value of the specified string resource or null if the resource could not be found or loaded. /// /// internal string GetStringResource(string assemblyName, string modulePath, string baseName, string resourceID) { string result = null; do // false loop { // Load the resource assembly Assembly assembly = LoadAssembly(assemblyName, modulePath); if (assembly == null) { break; } CultureInfo currentCulture = System.Globalization.CultureInfo.CurrentUICulture; Stream stream = null; // Get the resource stream from the manifest // Loop until we have reached the default culture (identified by an empty Name string in the // CultureInfo). do { string resourceStream = baseName; if (!String.IsNullOrEmpty(currentCulture.Name)) resourceStream += "." + currentCulture.Name; resourceStream += ".resources"; stream = assembly.GetManifestResourceStream(resourceStream); if (stream != null) { break; } if (String.IsNullOrEmpty(currentCulture.Name)) { break; } currentCulture = currentCulture.Parent; } while (true); if (stream == null) { break; } // Retrieve the string resource from the stream. result = GetString(stream, resourceID); } while (false); return result; } /// /// Loads the specified assembly using ReflectionOnlyLoad or ReflectionOnlyLoadFrom /// /// /// /// The FullName of the assembly to load. This takes precendence over the modulePath and /// will be passed to as a parameter to the ReflectionOnlyLoad. /// /// /// /// The full path of the assembly to load. This is used if the ReflectionOnlyLoad using the /// assemblyName doesn't load the assembly. It is passed as a parameter to the ReflectionOnlyLoadFrom API. /// /// /// /// An loaded instance of the specified resource assembly or null if the assembly couldn't be loaded. /// /// /// /// Since the intent of this method is to load resource assemblies, the standard culture fallback rules /// apply. If the assembly couldn't be loaded for the current culture we fallback to the parent culture /// until the neutral culture is reached or an assembly is loaded. /// /// private static Assembly LoadAssembly(string assemblyName, string modulePath) { Assembly assembly = null; AssemblyName assemblyNameObj = new AssemblyName(assemblyName); // Split the path up so we can add the culture directory. string moduleBase = Path.GetDirectoryName(modulePath); string moduleFile = Path.GetFileName(modulePath); CultureInfo currentCulture = System.Globalization.CultureInfo.CurrentUICulture; // Loop until we have reached the default culture (identified by an empty Name string in the // CultureInfo). do { assembly = LoadAssemblyForCulture(currentCulture, assemblyNameObj, moduleBase, moduleFile); if (assembly != null) { break; } if (String.IsNullOrEmpty(currentCulture.Name)) { break; } currentCulture = currentCulture.Parent; } while (true); return assembly; } /// /// Attempts to load the assembly for the specified culture /// /// /// /// The culture for which the assembly should be loaded. /// /// /// /// The name of the assembly without culture information (or at least undefined culture information). /// /// /// /// The directory containing the neutral culture assembly. /// /// /// /// The name of the assembly file. /// /// /// /// An instance of the loaded resource assembly or null if the assembly could not be loaded. /// /// private static Assembly LoadAssemblyForCulture( CultureInfo culture, AssemblyName assemblyName, string moduleBase, string moduleFile) { Assembly assembly = null; // Set the assembly FullName to contain the culture we are trying to load. assemblyName.CultureInfo = culture; try { assembly = Assembly.ReflectionOnlyLoad(assemblyName.FullName); } catch (FileLoadException) { } catch (BadImageFormatException) { } catch (FileNotFoundException) { } if (assembly != null) return assembly; // Try the resources DLL string oldAssemblyName = assemblyName.Name; try { assemblyName.Name = oldAssemblyName + ".resources"; assembly = Assembly.ReflectionOnlyLoad(assemblyName.FullName); } catch (FileLoadException) { } catch (BadImageFormatException) { } catch (FileNotFoundException) { } if (assembly != null) return assembly; assemblyName.Name = oldAssemblyName; // Add the culture directory into the file path string modulePath = Path.Combine(moduleBase, culture.Name); modulePath = Path.Combine(modulePath, moduleFile); if (File.Exists(modulePath)) { try { assembly = Assembly.ReflectionOnlyLoadFrom(modulePath); } catch (FileLoadException) { } catch (BadImageFormatException) { } catch (FileNotFoundException) { } } return assembly; } /// /// Retrieves the specified resource string from the resource stream. /// /// /// /// The resource stream containing the desired resource. /// /// /// /// The identifier of the string resource to retrieve from the stream. /// /// /// /// The resource string or null if the resourceID could not be found. /// /// private static string GetString(Stream stream, string resourceID) { string result = null; ResourceReader rr = new ResourceReader(stream); foreach (DictionaryEntry e in rr) { if (String.Equals(resourceID, (string)e.Key, StringComparison.OrdinalIgnoreCase)) { result = (string)e.Value; break; } } /* NTRAID#Windows Out Of Band Releases-920971-2005/09/30-JeffJon * Whidbey v2.0.50727 has a bug where GetResourceData throws an NullReferenceException if * the assembly used to get the ResourceReader was loaded with ReflectionOnlyLoad. This code * would be more efficient than the iteration in the foreach loop above and should be enabled * when we move to the RTM version of Whidbey. * string resourceType = null; byte[] resourceData = null; rr.GetResourceData(resourceID, out resourceType, out resourceData); */ return result; } } }