mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Added logic in AssemblyLoadContext to load assembly from GAC. (#3981)
This commit is contained in:
committed by
Dongbo Wang
parent
440590ef8a
commit
26a44abcec
+133
@@ -13,6 +13,8 @@ using System.Reflection;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Management.Automation
|
||||
{
|
||||
@@ -61,6 +63,11 @@ namespace System.Management.Automation
|
||||
/// </param>
|
||||
private PowerShellAssemblyLoadContext(string basePaths)
|
||||
{
|
||||
#if !UNIX
|
||||
// Set GAC related member variables to null
|
||||
_winDir = _gacPath32 = _gacPath64 = _gacPathMSIL = null;
|
||||
#endif
|
||||
|
||||
// FIRST: Validate and populate probing paths
|
||||
if (string.IsNullOrEmpty(basePaths))
|
||||
{
|
||||
@@ -103,6 +110,13 @@ namespace System.Management.Automation
|
||||
private readonly Dictionary<string, string> _coreClrTypeCatalog;
|
||||
private readonly Lazy<HashSet<string>> _availableDotNetAssemblyNames;
|
||||
|
||||
#if !UNIX
|
||||
private string _winDir;
|
||||
private string _gacPathMSIL;
|
||||
private string _gacPath32;
|
||||
private string _gacPath64;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Assembly cache across the AppDomain
|
||||
/// </summary>
|
||||
@@ -251,7 +265,15 @@ namespace System.Management.Automation
|
||||
// In this case, return null so that other Resolving event handlers can kick in to resolve the request.
|
||||
if (!isAssemblyFileFound || !isAssemblyFileMatching)
|
||||
{
|
||||
#if !UNIX
|
||||
//Try loading from GAC
|
||||
if(!TryFindInGAC(assemblyName, out asmFilePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
asmLoaded = asmFilePath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase)
|
||||
@@ -267,6 +289,99 @@ namespace System.Management.Automation
|
||||
return asmLoaded;
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
bool assemblyFound = false;
|
||||
assemblyFilePath = null;
|
||||
char dirSeparator = IO.Path.DirectorySeparatorChar;
|
||||
|
||||
if (PowerShellAssemblyLoadContextTestHooks.AllowGACLoading)
|
||||
{
|
||||
if(String.IsNullOrEmpty(_winDir))
|
||||
{
|
||||
//cache value of '_winDir' folder in member variable.
|
||||
_winDir = Environment.GetEnvironmentVariable("winDir");
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(_gacPathMSIL))
|
||||
{
|
||||
//cache value of '_gacPathMSIL' folder in member variable.
|
||||
_gacPathMSIL = $"{_winDir}{dirSeparator}Microsoft.NET{dirSeparator}assembly{dirSeparator}GAC_MSIL";
|
||||
}
|
||||
|
||||
assemblyFound = FindInGac(_gacPathMSIL, assemblyName, out assemblyFilePath);
|
||||
|
||||
if(!assemblyFound)
|
||||
{
|
||||
string gacBitnessAwarePath = null;
|
||||
|
||||
if(Environment.Is64BitProcess)
|
||||
{
|
||||
if(String.IsNullOrEmpty(_gacPath64))
|
||||
{
|
||||
//cache value of '_gacPath64' folder in member variable.
|
||||
_gacPath64 = $"{_winDir}{dirSeparator}Microsoft.NET{dirSeparator}assembly{dirSeparator}GAC_64";
|
||||
}
|
||||
|
||||
gacBitnessAwarePath = _gacPath64;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(String.IsNullOrEmpty(_gacPath32))
|
||||
{
|
||||
//cache value of '_gacPath32' folder in member variable.
|
||||
_gacPath32 = $"{_winDir}{dirSeparator}Microsoft.NET{dirSeparator}assembly{dirSeparator}GAC_32";
|
||||
}
|
||||
|
||||
gacBitnessAwarePath = _gacPath32;
|
||||
}
|
||||
|
||||
assemblyFound = FindInGac(gacBitnessAwarePath, assemblyName, out assemblyFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
return assemblyFound;
|
||||
}
|
||||
|
||||
// Find the assembly under 'gacRoot' and select the latest version.
|
||||
private bool FindInGac(string gacRoot, AssemblyName assemblyName, out string assemblyPath)
|
||||
{
|
||||
bool assemblyFound = false;
|
||||
assemblyPath = null;
|
||||
|
||||
char dirSeparator = IO.Path.DirectorySeparatorChar;
|
||||
string tempAssemblyDirPath = $"{gacRoot}{dirSeparator}{assemblyName.Name}";
|
||||
|
||||
if(Directory.Exists(tempAssemblyDirPath))
|
||||
{
|
||||
//Enumerate all directories, sort by name and select the last. This selects the latest version.
|
||||
var chosenVersionDirectory = Directory.GetDirectories(tempAssemblyDirPath).OrderBy(d => d).LastOrDefault();
|
||||
|
||||
if(!String.IsNullOrEmpty(chosenVersionDirectory))
|
||||
{
|
||||
//Select first or default as the directory will contain only one assembly. If nothing then default is null;
|
||||
var foundAssemblyPath = Directory.GetFiles(chosenVersionDirectory, $"{assemblyName.Name}*").FirstOrDefault();
|
||||
|
||||
if(!String.IsNullOrEmpty(foundAssemblyPath))
|
||||
{
|
||||
AssemblyName asmNameFound = AssemblyLoadContext.GetAssemblyName(foundAssemblyPath);
|
||||
if (IsAssemblyMatching(assemblyName, asmNameFound))
|
||||
{
|
||||
assemblyPath = foundAssemblyPath;
|
||||
assemblyFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return assemblyFound;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the specified assembly from cache
|
||||
/// </summary>
|
||||
@@ -400,6 +515,24 @@ namespace System.Management.Automation
|
||||
PowerShellAssemblyLoadContext.InitializeSingleton(basePaths);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test hooks for PowershellAssemblyLoadContext
|
||||
/// </summary>
|
||||
public static class PowerShellAssemblyLoadContextTestHooks
|
||||
{
|
||||
internal static bool AllowGACLoading = true;
|
||||
|
||||
/// <summary>This member is used for internal test purposes.</summary>
|
||||
public static void SetTestHook(string property, bool value)
|
||||
{
|
||||
var fieldInfo = typeof(PowerShellAssemblyLoadContextTestHooks).GetField(property, BindingFlags.Static | BindingFlags.NonPublic);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
fieldInfo.SetValue(null, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,9 +30,9 @@ Describe "Import-Module with ScriptsToProcess" -Tags "CI" {
|
||||
New-Item $moduleRootPath -ItemType Directory -Force | Out-Null
|
||||
Push-Location $moduleRootPath
|
||||
|
||||
"1 | Out-File out.txt -Append -NoNewline" | Out-File script1.ps1
|
||||
"2 | Out-File out.txt -Append -NoNewline" | Out-File script2.ps1
|
||||
New-ModuleManifest module1.psd1 -ScriptsToProcess script1.ps1
|
||||
"1 | Out-File out.txt -Append -NoNewline" | Out-File script1.ps1
|
||||
"2 | Out-File out.txt -Append -NoNewline" | Out-File script2.ps1
|
||||
New-ModuleManifest module1.psd1 -ScriptsToProcess script1.ps1
|
||||
New-ModuleManifest module2.psd1 -ScriptsToProcess script2.ps1 -NestedModules module1.psd1
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ Describe "Import-Module with ScriptsToProcess" -Tags "CI" {
|
||||
Remove-Item out.txt -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$testCases = @(
|
||||
@{ TestNameSuffix = 'for top-level module'; ipmoParms = @{'Name'='.\module1.psd1'}; Expected = '1' }
|
||||
@{ TestNameSuffix = 'for top-level and nested module'; ipmoParms = @{'Name'='.\module2.psd1'}; Expected = '21' }
|
||||
@{ TestNameSuffix = 'for top-level module when -Version is specified'; ipmoParms = @{'Name'='.\module1.psd1'; 'Version'='1.0'}; Expected = '1' }
|
||||
@{ TestNameSuffix = 'for top-level and nested module when -Version is specified'; ipmoParms = @{'Name'='.\module2.psd1'; 'Version'='1.0'}; Expected = '21' }
|
||||
$testCases = @(
|
||||
@{ TestNameSuffix = 'for top-level module'; ipmoParms = @{'Name'='.\module1.psd1'}; Expected = '1' }
|
||||
@{ TestNameSuffix = 'for top-level and nested module'; ipmoParms = @{'Name'='.\module2.psd1'}; Expected = '21' }
|
||||
@{ TestNameSuffix = 'for top-level module when -Version is specified'; ipmoParms = @{'Name'='.\module1.psd1'; 'Version'='1.0'}; Expected = '1' }
|
||||
@{ TestNameSuffix = 'for top-level and nested module when -Version is specified'; ipmoParms = @{'Name'='.\module2.psd1'; 'Version'='1.0'}; Expected = '21' }
|
||||
)
|
||||
|
||||
It "Verify ScriptsToProcess are executed <TestNameSuffix>" -TestCases $testCases {
|
||||
@@ -63,3 +63,28 @@ Describe "Import-Module with ScriptsToProcess" -Tags "CI" {
|
||||
Get-Content out.txt | Should Be $Expected
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Import-Module for Binary Modules in GAC" -Tags 'CI' {
|
||||
Context "Modules are not loaded from GAC" {
|
||||
BeforeAll {
|
||||
[System.Management.Automation.PowerShellAssemblyLoadContextTestHooks]::SetTestHook('AllowGACLoading', $false)
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
[System.Management.Automation.PowerShellAssemblyLoadContextTestHooks]::SetTestHook('AllowGACLoading', $true)
|
||||
}
|
||||
|
||||
It "Load PSScheduledJob from Windows Powershell Modules folder should fail" -Skip:(-not $IsWindows) {
|
||||
$modulePath = Join-Path $env:windir "System32/WindowsPowershell/v1.0/Modules/PSScheduledJob"
|
||||
{ Import-Module $modulePath -ErrorAction SilentlyContinue } | ShouldBeErrorId 'FormatXmlUpdateException,Microsoft.PowerShell.Commands.ImportModuleCommand'
|
||||
}
|
||||
}
|
||||
|
||||
Context "Modules are loaded from GAC" {
|
||||
It "Load PSScheduledJob from Windows Powershell Modules folder" -Skip:(-not $IsWindows) {
|
||||
$modulePath = Join-Path $env:windir "System32/WindowsPowershell/v1.0/Modules/PSScheduledJob"
|
||||
Import-Module $modulePath
|
||||
(Get-Command New-JobTrigger).Name | Should Be 'New-JobTrigger'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user