2020-03-24 11:08:37 -07:00
// Copyright (c) Microsoft Corporation.
2018-02-13 09:23:53 -08:00
// Licensed under the MIT License.
2016-03-30 14:20:52 -07:00
using System.Collections.Concurrent ;
using System.Collections.Generic ;
using System.Globalization ;
2019-12-10 01:03:53 +05:00
using System.IO ;
using System.Linq ;
2016-03-30 14:20:52 -07:00
using System.Runtime.InteropServices ;
using System.Reflection ;
using System.Runtime.Loader ;
namespace System.Management.Automation
{
/// <summary>
2019-01-06 04:40:24 +05:00
/// The powershell custom AssemblyLoadContext implementation.
2016-03-30 14:20:52 -07:00
/// </summary>
2021-07-04 12:49:52 +01:00
internal sealed partial class PowerShellAssemblyLoadContext
2016-03-30 14:20:52 -07:00
{
#region Resource_Strings
2017-01-16 13:31:14 -08:00
// We cannot use a satellite resources.dll to store resource strings for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll. This is because when retrieving resource strings, ResourceManager
2016-06-24 16:25:49 -07:00
// tries to load the satellite resources.dll using a probing approach, which will cause an infinite loop to PowerShellAssemblyLoadContext.Load(AssemblyName).
2016-03-30 14:20:52 -07:00
// Take the 'en-US' culture as an example. When retrieving resource string to construct an exception, ResourceManager calls Assembly.Load(..) in the following order to load the resource dll:
// 1. Load assembly with culture 'en-US' (Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.resources, Version=3.0.0.0, Culture=en-US, PublicKeyToken=31bf3856ad364e35)
2017-01-16 13:31:14 -08:00
// 2. Load assembly with culture 'en' (Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.resources, Version=3.0.0.0, Culture=en, PublicKeyToken=31bf3856ad364e35)
2016-08-26 16:46:03 -04:00
// When the first attempt fails, we again need to retrieve the resource string to construct another exception, which ends up with an infinite loop.
2016-03-30 14:20:52 -07:00
private const string BaseFolderDoesNotExist = "The base directory '{0}' does not exist." ;
private const string ManifestDefinitionDoesNotMatch = "Could not load file or assembly '{0}' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference." ;
2016-06-28 09:59:22 -07:00
private const string SingletonAlreadyInitialized = "The singleton of PowerShellAssemblyLoadContext has already been initialized." ;
2016-03-30 14:20:52 -07:00
#endregion Resource_Strings
#region Constructor
2016-06-24 16:25:49 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Initialize a singleton of PowerShellAssemblyLoadContext.
2016-06-24 16:25:49 -07:00
/// </summary>
2017-06-05 20:59:30 -07:00
internal static PowerShellAssemblyLoadContext InitializeSingleton ( string basePaths )
2016-06-24 16:25:49 -07:00
{
2016-07-29 13:02:49 -07:00
lock ( s_syncObj )
2016-06-28 09:59:22 -07:00
{
if ( Instance != null )
2019-12-10 01:03:53 +05:00
{
2016-06-28 09:59:22 -07:00
throw new InvalidOperationException ( SingletonAlreadyInitialized );
2019-12-10 01:03:53 +05:00
}
2016-06-28 09:59:22 -07:00
2017-06-05 20:59:30 -07:00
Instance = new PowerShellAssemblyLoadContext ( basePaths );
2016-06-28 09:59:22 -07:00
return Instance ;
}
2016-06-24 16:25:49 -07:00
}
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Constructor.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <param name="basePaths">
2017-06-05 20:59:30 -07:00
/// Base directory paths that are separated by semicolon ';'. They will be the default paths to probe assemblies.
/// The passed-in argument could be null or an empty string, in which case there is no default paths to probe assemblies.
2016-03-30 14:20:52 -07:00
/// </param>
2017-06-05 20:59:30 -07:00
private PowerShellAssemblyLoadContext ( string basePaths )
2016-03-30 14:20:52 -07:00
{
2017-06-15 13:25:59 -07:00
#if ! UNIX
// Set GAC related member variables to null
_winDir = _gacPath32 = _gacPath64 = _gacPathMSIL = null ;
#endif
2017-06-05 20:59:30 -07:00
// FIRST: Validate and populate probing paths
2016-06-24 16:56:43 -07:00
if ( string . IsNullOrEmpty ( basePaths ))
{
2017-06-05 20:59:30 -07:00
_probingPaths = Array . Empty < string >();
2016-06-28 22:57:20 -07:00
}
else
{
2020-03-13 17:46:05 +05:00
_probingPaths = basePaths . Split ( ';' , StringSplitOptions . RemoveEmptyEntries );
2017-06-05 20:59:30 -07:00
for ( int i = 0 ; i < _probingPaths . Length ; i ++)
2016-06-28 22:57:20 -07:00
{
2017-06-05 20:59:30 -07:00
string basePath = _probingPaths [ i ];
if (! Directory . Exists ( basePath ))
{
string message = string . Format ( CultureInfo . CurrentCulture , BaseFolderDoesNotExist , basePath );
2020-05-19 19:57:52 +01:00
throw new ArgumentException ( message , nameof ( basePaths ));
2017-06-05 20:59:30 -07:00
}
2018-12-23 22:20:06 -08:00
2017-06-05 20:59:30 -07:00
_probingPaths [ i ] = basePath . Trim ();
2016-06-28 22:57:20 -07:00
}
}
2017-06-05 20:59:30 -07:00
// NEXT: Initialize the CoreCLR type catalog dictionary [OrdinalIgnoreCase]
_coreClrTypeCatalog = InitializeTypeCatalog ();
_availableDotNetAssemblyNames = new Lazy < HashSet < string >>(
() => new HashSet < string >( _coreClrTypeCatalog . Values , StringComparer . Ordinal ));
2019-12-10 01:03:53 +05:00
// LAST: Register the 'Resolving' handler and 'ResolvingUnmanagedDll' handler on the default load context.
2017-06-05 20:59:30 -07:00
AssemblyLoadContext . Default . Resolving += Resolve ;
2019-12-10 01:03:53 +05:00
// Add last resort native dll resolver.
// Default order:
// 1. System.Runtime.InteropServices.DllImportResolver callbacks
// 2. AssemblyLoadContext.LoadUnmanagedDll()
// 3. AssemblyLoadContext.Default.ResolvingUnmanagedDll handlers
AssemblyLoadContext . Default . ResolvingUnmanagedDll += NativeDllHandler ;
2016-03-30 14:20:52 -07:00
}
#endregion Constructor
#region Fields
2016-07-29 13:02:49 -07:00
2020-12-09 03:52:00 +00:00
private static readonly object s_syncObj = new ();
2017-06-05 20:59:30 -07:00
private readonly string [] _probingPaths ;
private readonly string [] _extensions = new string [] { ".ni.dll" , ".dll" };
2016-06-28 09:59:22 -07:00
// CoreCLR type catalog dictionary
// - Key: namespace qualified type name (FullName)
// - Value: strong name of the TPA that contains the type represented by Key.
2016-07-29 13:02:49 -07:00
private readonly Dictionary < string , string > _coreClrTypeCatalog ;
2017-06-05 20:59:30 -07:00
private readonly Lazy < HashSet < string >> _availableDotNetAssemblyNames ;
2020-05-29 05:56:33 +01:00
2020-12-09 03:52:00 +00:00
private readonly HashSet < string > _denyListedAssemblies =
new ( StringComparer . OrdinalIgnoreCase ) { "System.Windows.Forms" };
2016-03-30 14:20:52 -07:00
2017-06-15 13:25:59 -07:00
#if ! UNIX
private string _winDir ;
private string _gacPathMSIL ;
private string _gacPath32 ;
private string _gacPath64 ;
#endif
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Assembly cache across the AppDomain.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <remarks>
/// We user the assembly short name (AssemblyName.Name) as the key.
2017-01-16 13:31:14 -08:00
/// According to the Spec of AssemblyLoadContext, "in the context of a given instance of AssemblyLoadContext, only one assembly with
2016-03-30 14:20:52 -07:00
/// a given name can be loaded. Attempt to load a second assembly with the same name and different MVID will result in an exception."
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// MVID is Module Version Identifier, which is a guid. Its purpose is solely to be unique for each time the module is compiled, and
/// it gets regenerated for every compilation. That means AssemblyLoadContext cannot handle loading two assemblies with the same name
2016-08-26 16:46:03 -04:00
/// but different versions, not even two assemblies with the exactly same code and version but built by two separate compilations.
2017-01-16 13:31:14 -08:00
///
2016-03-30 14:20:52 -07:00
/// Therefore, there is no need to use the full assembly name as the key. Short assembly name is sufficient.
/// </remarks>
2016-07-29 13:02:49 -07:00
private static readonly ConcurrentDictionary < string , Assembly > s_assemblyCache =
2020-12-09 03:52:00 +00:00
new ( StringComparer . OrdinalIgnoreCase );
2016-03-30 14:20:52 -07:00
#endregion Fields
2016-06-28 09:59:22 -07:00
#region Properties
/// <summary>
2019-01-06 04:40:24 +05:00
/// Singleton instance of PowerShellAssemblyLoadContext.
2016-06-28 09:59:22 -07:00
/// </summary>
2016-07-08 10:14:59 -07:00
internal static PowerShellAssemblyLoadContext Instance
2016-06-28 09:59:22 -07:00
{
get ; private set ;
}
2017-06-05 20:59:30 -07:00
/// <summary>
2019-05-10 15:34:27 -07:00
/// Get the namespace-qualified type names of all available .NET Core types shipped with PowerShell.
2017-06-05 20:59:30 -07:00
/// This is used for type name auto-completion in PS engine.
/// </summary>
internal IEnumerable < string > AvailableDotNetTypeNames
{
get { return _coreClrTypeCatalog . Keys ; }
}
/// <summary>
2019-05-10 15:34:27 -07:00
/// Get the assembly names of all available .NET Core assemblies shipped with PowerShell.
2017-06-05 20:59:30 -07:00
/// This is used for type name auto-completion in PS engine.
/// </summary>
internal HashSet < string > AvailableDotNetAssemblyNames
{
get { return _availableDotNetAssemblyNames . Value ; }
}
2016-06-28 09:59:22 -07:00
#endregion Properties
2017-06-05 20:59:30 -07:00
#region Internal_Methods
2016-06-24 16:25:49 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Get the current loaded assemblies.
2016-06-24 16:25:49 -07:00
/// </summary>
2017-06-05 20:59:30 -07:00
internal IEnumerable < Assembly > GetAssembly ( string namespaceQualifiedTypeName )
2016-06-28 09:59:22 -07:00
{
2017-06-05 20:59:30 -07:00
// If 'namespaceQualifiedTypeName' is specified and it's a CoreCLR framework type,
// then we only return that specific TPA assembly.
if (! string . IsNullOrEmpty ( namespaceQualifiedTypeName ))
{
if ( _coreClrTypeCatalog . TryGetValue ( namespaceQualifiedTypeName , out string tpaStrongName ))
{
try
{
return new Assembly [] { GetTrustedPlatformAssembly ( tpaStrongName ) };
}
catch ( FileNotFoundException ) { }
}
}
2016-06-28 09:59:22 -07:00
2017-06-05 20:59:30 -07:00
// Otherwise, we return null
return null ;
2016-06-28 09:59:22 -07:00
}
2019-12-10 01:03:53 +05:00
/// <summary>
/// If a managed dll has native dependencies the handler will try to find these native dlls.
/// 1. Gets the managed.dll location (folder)
/// 2. Based on OS name and architecture name builds subfolder name where it is expected the native dll resides:
/// 3. Loads the native dll
///
/// managed.dll folder
/// |
/// |--- 'win-x64' subfolder
/// | |--- native.dll
/// |
/// |--- 'win-x86' subfolder
/// | |--- native.dll
/// |
/// |--- 'win-arm' subfolder
/// | |--- native.dll
/// |
/// |--- 'win-arm64' subfolder
/// | |--- native.dll
/// |
/// |--- 'linux-x64' subfolder
/// | |--- native.so
/// |
/// |--- 'linux-x86' subfolder
/// | |--- native.so
/// |
/// |--- 'linux-arm' subfolder
/// | |--- native.so
/// |
/// |--- 'linux-arm64' subfolder
/// | |--- native.so
/// |
/// |--- 'osx-x64' subfolder
/// | |--- native.dylib
2022-06-04 11:20:59 -04:00
/// |
/// |--- 'osx-arm64' subfolder
/// | |--- native.dylib
2019-12-10 01:03:53 +05:00
/// </summary>
internal static IntPtr NativeDllHandler ( Assembly assembly , string libraryName )
{
s_nativeDllSubFolder ??= GetNativeDllSubFolderName ( out s_nativeDllExtension );
2020-02-07 12:03:16 -08:00
string folder = Path . GetDirectoryName ( assembly . Location );
string fullName = Path . Combine ( folder , s_nativeDllSubFolder , libraryName ) + s_nativeDllExtension ;
2019-12-10 01:03:53 +05:00
2020-02-07 12:03:16 -08:00
return NativeLibrary . TryLoad ( fullName , out IntPtr pointer ) ? pointer : IntPtr . Zero ;
2019-12-10 01:03:53 +05:00
}
2017-06-05 20:59:30 -07:00
#endregion Internal_Methods
#region Private_Methods
2016-06-28 09:59:22 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// The handler for the Resolving event.
2016-06-28 09:59:22 -07:00
/// </summary>
private Assembly Resolve ( AssemblyLoadContext loadContext , AssemblyName assemblyName )
2016-03-30 14:20:52 -07:00
{
// Probe the assembly cache
Assembly asmLoaded ;
if ( TryGetAssemblyFromCache ( assemblyName , out asmLoaded ))
return asmLoaded ;
// Prepare to load the assembly
2016-07-29 13:02:49 -07:00
lock ( s_syncObj )
2016-03-30 14:20:52 -07:00
{
// Probe the cache again in case it's already loaded
if ( TryGetAssemblyFromCache ( assemblyName , out asmLoaded ))
return asmLoaded ;
// Search the specified assembly in probing paths, and load it through 'LoadFromAssemblyPath' if the file exists and matches the requested AssemblyName.
// If the CultureName of the requested assembly is not NullOrEmpty, then it's a resources.dll and we need to search corresponding culture sub-folder.
bool isAssemblyFileFound = false , isAssemblyFileMatching = false ;
string asmCultureName = assemblyName . CultureName ?? string . Empty ;
string asmFilePath = null ;
2017-06-05 20:59:30 -07:00
for ( int i = 0 ; i < _probingPaths . Length ; i ++)
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
string probingPath = _probingPaths [ i ];
2016-03-30 14:20:52 -07:00
string asmCulturePath = Path . Combine ( probingPath , asmCultureName );
2016-07-29 13:02:49 -07:00
for ( int k = 0 ; k < _extensions . Length ; k ++)
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
string asmFileName = assemblyName . Name + _extensions [ k ];
2016-03-30 14:20:52 -07:00
asmFilePath = Path . Combine ( asmCulturePath , asmFileName );
if ( File . Exists ( asmFilePath ))
{
isAssemblyFileFound = true ;
2017-06-05 20:59:30 -07:00
AssemblyName asmNameFound = AssemblyLoadContext . GetAssemblyName ( asmFilePath );
2016-03-30 14:20:52 -07:00
if ( IsAssemblyMatching ( assemblyName , asmNameFound ))
{
isAssemblyFileMatching = true ;
break ;
}
}
}
if ( isAssemblyFileFound && isAssemblyFileMatching )
{
break ;
}
}
2016-06-28 09:59:22 -07:00
// We failed to find the assembly file; or we found the file, but the assembly file doesn't match the request.
// In this case, return null so that other Resolving event handlers can kick in to resolve the request.
if (! isAssemblyFileFound || ! isAssemblyFileMatching )
2016-03-30 14:20:52 -07:00
{
2017-06-15 13:25:59 -07:00
#if ! UNIX
2019-01-06 06:51:20 +05:00
// Try loading from GAC
2017-09-29 17:26:55 -07:00
if (! TryFindInGAC ( assemblyName , out asmFilePath ))
2017-06-15 13:25:59 -07:00
{
2019-04-24 19:27:22 +05:00
return null ;
2017-06-15 13:25:59 -07:00
}
#else
2016-06-28 09:59:22 -07:00
return null ;
2017-06-15 13:25:59 -07:00
#endif
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
asmLoaded = asmFilePath . EndsWith ( ".ni.dll" , StringComparison . OrdinalIgnoreCase )
? loadContext . LoadFromNativeImagePath ( asmFilePath , null )
2016-06-28 09:59:22 -07:00
: loadContext . LoadFromAssemblyPath ( asmFilePath );
2016-03-30 14:20:52 -07:00
if ( asmLoaded != null )
{
// Add the loaded assembly to the cache
2016-07-29 13:02:49 -07:00
s_assemblyCache . TryAdd ( assemblyName . Name , asmLoaded );
2016-03-30 14:20:52 -07:00
}
}
return asmLoaded ;
}
2017-06-15 13:25:59 -07:00
#if ! UNIX
// Try to find the assembly in GAC by looking up the directories in well know locations.
// First try to find in GAC_MSIL, then depending on process bitness; GAC_64 or GAC32.
// If there are multiple version of the assembly, load the latest.
private bool TryFindInGAC ( AssemblyName assemblyName , out string assemblyFilePath )
{
assemblyFilePath = null ;
2019-12-10 09:51:46 -08:00
if ( _denyListedAssemblies . Contains ( assemblyName . Name ))
2018-05-07 16:38:27 -07:00
{
// DotNet catches and throws a new exception with no inner exception
// We cannot change the message DotNet returns.
return false ;
}
2017-09-29 17:26:55 -07:00
if ( Internal . InternalTestHooks . DisableGACLoading )
{
return false ;
}
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( _winDir ))
2017-06-15 13:25:59 -07:00
{
2019-01-06 06:51:20 +05:00
// cache value of '_winDir' folder in member variable.
2017-09-29 17:26:55 -07:00
_winDir = Environment . GetEnvironmentVariable ( "winDir" );
}
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( _gacPathMSIL ))
2017-09-29 17:26:55 -07:00
{
2019-01-06 06:51:20 +05:00
// cache value of '_gacPathMSIL' folder in member variable.
2022-08-02 23:06:01 -07:00
_gacPathMSIL = Path . Join ( _winDir , "Microsoft.NET" , "assembly" , "GAC_MSIL" );
2017-09-29 17:26:55 -07:00
}
2022-08-02 23:06:01 -07:00
bool assemblyFound = FindInGac ( _gacPathMSIL , assemblyName , out assemblyFilePath );
2017-09-29 17:26:55 -07:00
if (! assemblyFound )
{
2022-08-02 20:03:13 -07:00
string gacBitnessAwarePath ;
2017-09-29 17:26:55 -07:00
if ( Environment . Is64BitProcess )
2017-06-15 13:25:59 -07:00
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( _gacPath64 ))
2017-06-15 13:25:59 -07:00
{
2022-08-02 20:03:13 -07:00
var gacName = RuntimeInformation . ProcessArchitecture == Architecture . Arm64 ? "GAC_Arm64" : "GAC_64" ;
_gacPath64 = Path . Join ( _winDir , "Microsoft.NET" , "assembly" , gacName );
2017-06-15 13:25:59 -07:00
}
2017-09-29 17:26:55 -07:00
gacBitnessAwarePath = _gacPath64 ;
2017-06-15 13:25:59 -07:00
}
2017-09-29 17:26:55 -07:00
else
{
2018-12-31 15:10:15 +05:00
if ( string . IsNullOrEmpty ( _gacPath32 ))
2017-09-29 17:26:55 -07:00
{
2022-08-02 20:03:13 -07:00
_gacPath32 = Path . Join ( _winDir , "Microsoft.NET" , "assembly" , "GAC_32" );
2017-09-29 17:26:55 -07:00
}
gacBitnessAwarePath = _gacPath32 ;
}
assemblyFound = FindInGac ( gacBitnessAwarePath , assemblyName , out assemblyFilePath );
2017-06-15 13:25:59 -07:00
}
return assemblyFound ;
}
// Find the assembly under 'gacRoot' and select the latest version.
2020-11-27 08:16:31 +00:00
private static bool FindInGac ( string gacRoot , AssemblyName assemblyName , out string assemblyPath )
2017-06-15 13:25:59 -07:00
{
bool assemblyFound = false ;
assemblyPath = null ;
2022-08-02 23:06:01 -07:00
string tempAssemblyDirPath = Path . Join ( gacRoot , assemblyName . Name );
2017-06-15 13:25:59 -07:00
2017-09-29 17:26:55 -07:00
if ( Directory . Exists ( tempAssemblyDirPath ))
2017-06-15 13:25:59 -07:00
{
2019-01-06 06:51:20 +05:00
// Enumerate all directories, sort by name and select the last. This selects the latest version.
2021-04-13 00:44:05 +05:00
var chosenVersionDirectory = Directory . EnumerateDirectories ( tempAssemblyDirPath ). OrderBy ( static d => d ). LastOrDefault ();
2017-06-15 13:25:59 -07:00
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( chosenVersionDirectory ))
2017-06-15 13:25:59 -07:00
{
2019-01-06 06:51:20 +05:00
// Select first or default as the directory will contain only one assembly. If nothing then default is null;
2020-12-03 22:35:25 +00:00
var foundAssemblyPath = Directory . EnumerateFiles ( chosenVersionDirectory , $"{assemblyName.Name}*" ). FirstOrDefault ();
2017-06-15 13:25:59 -07:00
2018-12-31 15:10:15 +05:00
if (! string . IsNullOrEmpty ( foundAssemblyPath ))
2017-06-15 13:25:59 -07:00
{
AssemblyName asmNameFound = AssemblyLoadContext . GetAssemblyName ( foundAssemblyPath );
if ( IsAssemblyMatching ( assemblyName , asmNameFound ))
{
assemblyPath = foundAssemblyPath ;
assemblyFound = true ;
}
}
}
}
return assemblyFound ;
}
#endif
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-06 04:40:24 +05:00
/// Try to get the specified assembly from cache.
2016-03-30 14:20:52 -07:00
/// </summary>
2020-11-27 08:16:31 +00:00
private static bool TryGetAssemblyFromCache ( AssemblyName assemblyName , out Assembly asmLoaded )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
if ( s_assemblyCache . TryGetValue ( assemblyName . Name , out asmLoaded ))
2016-03-30 14:20:52 -07:00
{
// Check if loaded assembly matches the request
if ( IsAssemblyMatching ( assemblyName , asmLoaded . GetName ()))
return true ;
// In the context of a given instance of AssemblyLoadContext, only one assembly with the
// same name can be loaded. So we throw exception if assembly doesn't match the request.
ThrowFileLoadException (
ManifestDefinitionDoesNotMatch ,
assemblyName . FullName );
}
return false ;
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Check if the loaded assembly matches the request.
2016-03-30 14:20:52 -07:00
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="requestedAssembly">AssemblyName of the requested assembly.</param>
/// <param name="loadedAssembly">AssemblyName of the loaded assembly.</param>
2016-03-30 14:20:52 -07:00
/// <returns></returns>
2020-11-27 08:16:31 +00:00
private static bool IsAssemblyMatching ( AssemblyName requestedAssembly , AssemblyName loadedAssembly )
2016-03-30 14:20:52 -07:00
{
//
// We use the same rules as CoreCLR loader to compare the requested assembly and loaded assembly:
// 1. If 'Version' of the requested assembly is specified, then the requested version should be less or equal to the loaded version;
// 2. If 'CultureName' of the requested assembly is specified (not NullOrEmpty), then the CultureName of the loaded assembly should be the same;
// 3. If 'PublicKeyToken' of the requested assembly is specified (not Null or EmptyArray), then the PublicKenToken of the loaded assembly should be the same.
//
// Version of the requested assembly should be the same or before the version of loaded assembly
if ( requestedAssembly . Version != null && requestedAssembly . Version . CompareTo ( loadedAssembly . Version ) > 0 )
{
return false ;
}
// CultureName of requested assembly and loaded assembly should be the same
string requestedCultureName = requestedAssembly . CultureName ;
if (! string . IsNullOrEmpty ( requestedCultureName ) && ! requestedCultureName . Equals ( loadedAssembly . CultureName , StringComparison . OrdinalIgnoreCase ))
{
return false ;
}
// PublicKeyToken should be the same, unless it's not specified in the requested assembly
byte [] requestedPublicKeyToken = requestedAssembly . GetPublicKeyToken ();
byte [] loadedPublicKeyToken = loadedAssembly . GetPublicKeyToken ();
if ( requestedPublicKeyToken != null && requestedPublicKeyToken . Length > 0 )
{
2020-07-30 16:06:38 -07:00
if ( loadedPublicKeyToken == null || requestedPublicKeyToken . Length != loadedPublicKeyToken . Length )
2016-03-30 14:20:52 -07:00
return false ;
for ( int i = 0 ; i < requestedPublicKeyToken . Length ; i ++)
{
if ( requestedPublicKeyToken [ i ] != loadedPublicKeyToken [ i ])
return false ;
}
}
return true ;
}
/// <summary>
/// Get the TPA that is represented by the specified assembly strong name.
/// </summary>
/// <param name="tpaStrongName">
/// The assembly strong name of a CoreCLR Trusted_Platform_Assembly
/// </param>
2020-11-27 08:16:31 +00:00
private static Assembly GetTrustedPlatformAssembly ( string tpaStrongName )
2016-03-30 14:20:52 -07:00
{
2016-07-08 10:14:59 -07:00
// We always depend on the default context to load the TPAs that are recorded in
// the type catalog.
// - If the requested TPA is already loaded, then 'Assembly.Load' will just get
// it back from the cache of default context.
// - If the requested TPA is not loaded yet, then 'Assembly.Load' will make the
// default context to load it
2020-12-09 03:52:00 +00:00
AssemblyName assemblyName = new ( tpaStrongName );
2016-07-08 10:14:59 -07:00
Assembly asmLoaded = Assembly . Load ( assemblyName );
return asmLoaded ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Throw FileLoadException.
2016-03-30 14:20:52 -07:00
/// </summary>
2020-11-27 08:16:31 +00:00
private static void ThrowFileLoadException ( string errorTemplate , params object [] args )
2016-03-30 14:20:52 -07:00
{
string message = string . Format ( CultureInfo . CurrentCulture , errorTemplate , args );
throw new FileLoadException ( message );
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Throw FileNotFoundException.
2016-03-30 14:20:52 -07:00
/// </summary>
2020-11-27 08:16:31 +00:00
private static void ThrowFileNotFoundException ( string errorTemplate , params object [] args )
2016-03-30 14:20:52 -07:00
{
string message = string . Format ( CultureInfo . CurrentCulture , errorTemplate , args );
throw new FileNotFoundException ( message );
}
2019-12-10 01:03:53 +05:00
private static string s_nativeDllSubFolder ;
private static string s_nativeDllExtension ;
private static string GetNativeDllSubFolderName ( out string ext )
{
string folderName = string . Empty ;
ext = string . Empty ;
var processArch = RuntimeInformation . ProcessArchitecture . ToString (). ToLowerInvariant ();
2022-01-27 21:53:52 +05:00
if ( Platform . IsWindows )
2019-12-10 01:03:53 +05:00
{
folderName = "win-" + processArch ;
ext = ".dll" ;
}
2022-01-27 21:53:52 +05:00
else if ( Platform . IsLinux )
2019-12-10 01:03:53 +05:00
{
folderName = "linux-" + processArch ;
ext = ".so" ;
}
2022-01-27 21:53:52 +05:00
else if ( Platform . IsMacOS )
2019-12-10 01:03:53 +05:00
{
2022-06-04 11:20:59 -04:00
folderName = "osx-" + processArch ;
2019-12-10 01:03:53 +05:00
ext = ".dylib" ;
}
return folderName ;
}
2016-03-30 14:20:52 -07:00
#endregion Private_Methods
}
/// <summary>
/// This is the managed entry point for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll.
/// </summary>
2020-11-04 01:16:36 +00:00
public static class PowerShellAssemblyLoadContextInitializer
2016-03-30 14:20:52 -07:00
{
/// <summary>
2016-06-28 09:59:22 -07:00
/// Create a singleton of PowerShellAssemblyLoadContext.
/// Then register to the Resolving event of the load context that loads this assembly.
2016-03-30 14:20:52 -07:00
/// </summary>
2016-06-28 09:59:22 -07:00
/// <remarks>
/// This method is to be used by native host whose TPA list doesn't include PS assemblies, such as the
2017-10-17 17:25:11 -07:00
/// in-box Nano powershell, the PS remote WinRM plugin, in-box Nano DSC and in-box Nano SCOM agent.
2016-06-28 09:59:22 -07:00
/// </remarks>
/// <param name="basePaths">
/// Base directory paths that are separated by semicolon ';'.
/// They will be the default paths to probe assemblies.
/// </param>
2020-11-04 19:13:56 +00:00
public static void SetPowerShellAssemblyLoadContext ([ MarshalAs ( UnmanagedType . LPWStr )] string basePaths )
2016-03-30 14:20:52 -07:00
{
2016-06-24 16:56:43 -07:00
if ( string . IsNullOrEmpty ( basePaths ))
2020-05-19 19:57:52 +01:00
throw new ArgumentNullException ( nameof ( basePaths ));
2016-06-24 16:56:43 -07:00
2017-06-05 20:59:30 -07:00
PowerShellAssemblyLoadContext . InitializeSingleton ( basePaths );
2016-03-30 14:20:52 -07:00
}
}
2021-04-28 17:28:41 -04:00
/// <summary>
/// Provides helper functions to faciliate calling managed code from a native PowerShell host.
/// </summary>
public static unsafe class PowerShellUnsafeAssemblyLoad
{
/// <summary>
/// Load an assembly in memory from unmanaged code.
/// </summary>
/// <remarks>
/// This API is covered by the experimental feature 'PSLoadAssemblyFromNativeCode',
/// and it may be deprecated and removed in future.
/// </remarks>
/// <param name="data">Unmanaged pointer to assembly data buffer.</param>
/// <param name="size">Size in bytes of the assembly data buffer.</param>
/// <returns>Returns zero on success and non-zero on failure.</returns>
[UnmanagedCallersOnly]
public static int LoadAssemblyFromNativeMemory ( IntPtr data , int size )
{
try
{
using var stream = new UnmanagedMemoryStream (( byte *) data , size );
AssemblyLoadContext . Default . LoadFromStream ( stream );
return 0 ;
}
catch
{
return - 1 ;
}
}
}
2016-03-30 14:20:52 -07:00
}