PowerShell:为什么我能够在一个会话中加载同一个 .NET 程序集的多个版本并且 "bypass Dependency hell"?

PowerShell: why am I able to load multiple versions of the same .NET assembly in one session and also "bypass Dependency hell"?

我在 Windows 10 x64,PowerShell 版本 5.1 中工作。

我有多个版本的 .NET Framework 4.0 程序集 (.dll) 是用 C# 编写的(我自己)。程序集未签名。它们的版本通过 [assembly: AssemblyVersion("X.X.X.X")]AssemblyInfo.cs 中设置。 [assembly: AssemblyFileVersion("X.X.X.X")] 标签 在我的 AssemblyInfo.cs 中不存在!这些程序集在常规 .NET Framework 应用程序中使用,它们不是专门的 PowerShell 模块,也没有清单文件。

我在一些 PowerShell 脚本中使用这个程序集来创建对象并从中调用方法。

我有这个程序集的两个版本,比方说 1.1.1.1001.2.1.100。当我通过 Import-Module "D:\path\to_v1.1\MyAssembly.dll" 在 PowerShell 中导入 1.1.1.100 版本时,它工作得很好。当我调用 Get-Module 时,我在列表中看到这个导入的程序集:

ModuleType Version    Name
---------- -------    ----
Binary     1.1.1.100  MyAssembly

现在事情开始变得有趣了。我导入了另一个版本的程序集 Import-Module "D:\another\path\to_v1.2\MyAssembly.dll"。它再次工作得很好。所以这是我的问题:

Q1. 为什么不显示错误?我希望得到一个错误:我正在尝试加载一个名称相同但版本不同的程序集。我认为您无法在一个上下文中加载同一程序集的两个不同版本。如何处理这些情况,使用哪些加载上下文?也许第二个版本根本没有加载?

Q2. Get-Module 现在显示 两个 具有相同名称和版本的模块,如下所示:

ModuleType Version    Name
---------- -------    ----
Binary     1.1.1.100  MyAssembly
Binary     1.1.1.100  MyAssembly

我先导入哪个版本并不重要。在第二个 Import-Module 中, 最早导入的版本 Get-Module 列表中重复。我也只能使用最早导入的程序集版本中的 types/methods。

Q3. 我的程序集对我的其他程序集有一些依赖性(在同一文件夹中)。这些依赖项会自动解析并隐式“导入”到当前会话(我可以毫无问题地使用它们的类型)。主要 MyAssembly.dll 的每个版本都依赖于这些辅助程序集的不同版本。当我导入另一个版本的 MyAssembly 时,我也没有收到有关版本冲突的错误。再一次,我只能使用最早导入的二级程序集的类型。我读过“依赖地狱”,这 应该是不可能的 —— 那怎么可能呢?

我用 NuGet 包 Microsoft.CodeAnalysis.CSharp.dll、版本 3.4.03.11.0 进行了相同的实验。结果是一样的:版本及其依赖导入没问题,但只有最早导入的版本可用。

总结

当我导入相同程序集的不同版本时,没有出现任何错误,但只有最早导入的版本可用。 我想了解为什么我能够在一个会话中无错误地加载程序集的多个版本,PowerShell 通常如何处理这些情况以及为什么它显示两个具有相同版本的模块,为什么我没有得到依赖地狱。

我想了解发生了什么,而不仅仅是“让它工作”。

我在这里错过了什么?

谢谢!

在您的方案中,您的模块是 独立的 .NET 程序集,它实现了 PowerShell cmdlet。

在 Windows PowerShell 和 PowerShell (Core) 7+ 中,所有程序集都加载到 相同的:

  • WindowsPowerShell中的应用程序域,基于。 NET 框架.

  • PowerShell (Core) 7+ 中的 ALC(程序集加载上下文),它基于 .NET Core / .NET 5+.

默认情况下,不支持加载同一程序集的多个版本,第一个支持加载到会话中获胜。

随后 尝试加载 不同版本 的程序集 :

  • 在 WindowsPowerShell 中被 悄悄忽略 - 无论您是否使用 Import-Module, Add-Type -LiteralPath 或其底层 .NET API 方法 [System.Reflection.Assembly]::LoadFrom()(一定要将 完整 文件路径传递给后者,因为 PowerShell 正在运行dir. 通常与 .NET 不同;该方法输出一个表示程序集的对象,表面上是刚刚加载的,但它实际上表示 最初 加载的版本)。

  • 在 Powershell (Core) 7+

    中导致语句终止 错误
    Assembly with same name is already loaded.
    

注意:您 可以 加载具有不同版本的程序集,即通过 [System.Reflection.Assembly]::LoadFile() 方法(注意 File 而不是 From), 但你可以使用它的类型的唯一方法是通过反射.


据我所知:

  • 没有 cmdlet 实现 程序集 的解决方案,和你的情况一样。

    • 通过 cmdlet-implementing 我的意思是模块的 cmdlet 本身实现为 .NET 类 存储在程序集中,而不是 cmdlet在 PowerShell 代码*.psm1 脚本模块文件中实施。

    • 无论您在会话中首先导入/加载哪个版本的 cmdlet 实现程序集,在会话的其余部分都是唯一可用的;程序集一旦加载,便无法卸载。

    • 另一种表达方式:虽然您可以在磁盘磁盘上拥有此类模块的并排版本,但您无法加载他们同时进入一个会话,据我所知——这只适用于基于脚本的模块(见下一点)。

  • 有一些非常重要的解决方案可以将多个版本的 helper 程序集加载到同一会话中Resolving PowerShell module assembly dependency conflicts 中对此进行了详细说明,其中还提供了广泛的背景信息。

    • 也就是说,如果您的 cmdlet 在 PowerShell 代码 中实现并且该代码仅使用 auxiliary .NET 程序集,则链接文档提供这些程序集的加载版本默认会与其他模块加载的不同版本或同一模块的不同版本发生冲突。