PowerShell核心:解决集会冲突
我发现了一个有趣的案例,我无法完全理解。
我的运行时 - PowerShell 7.2.4
我有两个PowerShell核心模块: net6powershellmodule
和 netStandstandArdPowersHellModule
。
net6powershellmodule.csproj :
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<None Remove="Net6PowerShellModule.psd1" />
</ItemGroup>
<ItemGroup>
<Content Include="Net6PowerShellModule.psd1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="System.Management.Automation" Version="7.2.4" />
</ItemGroup>
</Project>
test net6cmdlet.cs
using System.Management.Automation;
namespace Net6PowerShellModule
{
[Cmdlet("Test", "Net6Cmdlet")]
public class TestNet6Cmdlet : PSCmdlet
{
protected override void ProcessRecord()
{
var type = typeof(Microsoft.Extensions.DependencyInjection.IServiceCollection);
WriteObject("Net6 Cmdlet Run.");
}
}
}
net6powershellmodule.psd1
@{
RootModule = 'Net6PowerShellModule.dll'
ModuleVersion = '0.0.1'
GUID = '8c1bd929-32bd-44c3-af6b-d9dd261e34f3'
CmdletsToExport = 'Test-Net6Cmdlet'
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<None Remove="NetstandardPowerShellModule.psd1" />
</ItemGroup>
<ItemGroup>
<Content Include="NetstandardPowerShellModule.psd1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
<PackageReference Include="System.Management.Automation" Version="6.1.2" />
</ItemGroup>
</Project>
net standstandArdPowerShellModule.csproj.csproj : 。
using System.Management.Automation;
namespace NetstandardPowerShellModule
{
[Cmdlet("Test", "NetstandardCmdlet")]
public class TestNetstandardCmdlet : PSCmdlet
{
protected override void ProcessRecord()
{
var type = typeof(Microsoft.Extensions.DependencyInjection.IServiceCollection);
WriteObject("Netstandard 2.0 Cmdlet Run.");
}
}
}
@{
RootModule = 'NetstandardPowerShellModule.dll'
ModuleVersion = '0.0.1'
GUID = 'eef615d0-aecf-4e89-8f4c-53174f8c99d6'
CmdletsToExport = 'Test-NetstandardCmdlet'
}
-
-
- test-net6cmdlet
- 运行
test-netstandardcmdlet
结果 - 一切都正确运行。唯一的加载程序集是 microsoft.extensions.dependentiondoction.abstractions
版本 6.0.0
。
- 导入
net6powershellmodule
- import
netstandstandardpowershellmodule
- 运行
test-netstandstandardcmdlet
- 运行
test-net6cmdlet
。
结果 - 错误: test net6cmdlet:无法加载文件或汇编'microsoft.extensions.dependencyIndoction.Abstractions,版本= 6.0.0.0,culture =中性=中性,public Keytoken = adb9793829dddae60'。找不到或加载特定文件。 (0x80131621)
随后的尝试加载不同版本的汇编: 在PowerShell(Core)7+中导致语句终止错误 带有相同名称的汇编已加载。
为什么第一个方案工作?
如果我添加 quired Assemblies = @('microsoft.extensions.dependentionspoction.abstractions.dll.dll')
line line在两个模块表现中,我都有一个预期的行为:不管先加载哪个模块,我仍然得到错误:带有同名的汇编已经加载。
更新1
我已经看过的文章:
- https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat /nesion-dipendenty-conflicts
- powerShell:为什么我能够在一个会话中加载多个版本的同一.NET组件,也可以“绕过依赖性地狱”?
- https://devblogs.microsoft.com/powers.com/powershell/resolving-powershell/resolving-powershell-module-module-module-module-modelend-conpelency-conflict-conflicts-conflicts/ << /a> - 本文解释了有关汇编负载上下文的很多解释,但是我仍然无法理解
requientsemblies
在.psd1
文件中定义的差异或使用代码中的依赖项。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
tl; dr
在创作模块时,最好使用 模块清单(
.psd1
file),带有quiends> quiendsAssemblies
要声明依赖(helper)组件的键 /strong>,因为它使此类组件的加载可预测的,,因为它在模块导入时发生。在 Windows PowerShell 中,尝试加载已经加载的已经加载的汇编的不同版本的尝试被安静地忽略,并使新导入的模块用于使用< em>也已经加载了汇编,可以或可能不起作用。
in powershell(core) (v6+),尝试加载已经加载的已经加载的组件的不同版本的尝试是 。
。
如果您使用 intemit 基于.NET的机制的依赖组件的加载,则无法保证这些依赖关系的时机,并且在PowerShell(core)中,您将绕过防止尝试加载已经加载的已经加载的程序集的不同版本的检查 - 它可能有效,也可能不起作用,并且只有在以后的加载尝试请求 版本的情况下才允许。<<<<<<<<<<<< /p>
非平凡的解决方法可靠 给定组件的不同版本并排 。
。
背景信息
之一您的链接之一,解决powershell模块组件依赖性冲突,包含所有相关信息,但它是一本带有复杂,深入的信息的冗长读取,并且无法阐明 powerShell的方式)(v6+)< / em>覆盖了基础.NET(CORE) / .NET 5+框架的行为。
术语:为简单起见,我将使用以下术语:
Windows PowerShell 是遗产,船只,备用窗口的Windows-Windows-PowerShell,其最新和最新的版本最终版本为5.1.x。
powerShell core (现在正式出现 powershell )是现代的,跨平台的,按需安装的powershell版本,其第一个(但现在过时的)版本为v6,其当前版本为v7 .2.6。
基本问题< / strong>:
在.NET框架 / CLR(通用语言运行时)级别,基本问题是无法并排加载不同版本的不同版本相同应用域(.net Framework)/ alc ( assembly-load-load上下文,.net core):将的第一个加载到会话中悄悄地赢得,而.NET核心施加了额外的限制,即仅尝试加载下下版本成功(见下文)。
PowerShell仅使用单个应用程序域 / ALC,包括用于模块,需要解决方案< / strong>才能并排的不同版本的A(依赖)组件。链接的文章详细介绍了各种解决方法; 强大的,通用的,进程的解决方法需要Powershell核心,并且仅限于 binary (编译)模块,并且是非平凡的;有限的解决方法,一些程序外,可用于Windows PowerShell和Cross-Powershell-Edition代码。
行为没有解决方法:
Windows PowerShell和.NET框架:
在已加载版本,,不考虑要求的版本高还是更低与已经加载的版本相比。- 奇怪的是,当呼叫者使用反射带有
-
-
-
-
- 值得注意的是,如果以后的呼叫者试图加载a 更高> 版本的 fail ,因为它可能依赖于已经加载的较低的功能版本没有,但是即使是逆场景也不能成功,鉴于更高版本可能引入了破坏更改。
typeof
来检查装配及其刚加载的类型(无论是隐式还是明确),请求请求汇编版本已报告 - 即使它是已经加载的汇编及其类型实际上是 。Windows PowerShell确实 使用自定义功能覆盖此行为,尽管您是否使用模块清单(
.psd1
) file)带有essircealsemblies
输入可以在上有所作为依赖依赖的组件(请参见下文)。。
UPSHOT :
尝试加载已加载的汇编的不同版本永不失败,让呼叫者使用已加载的版本。
此可能会或可能不工作,您只会在相关组件中的类型时注意到 (构建实例,属性访问,方法调用,...)。
PowerShell Core和.net Core :
.net core :
悄悄忽略随后的尝试加载已经加载的汇编的A 下版本,并且使呼叫者使用已经加载的版本< /em>
抛出异常,以尝试加载A 更高> 版本:
无法找到或加载特定文件。 (0x80131621)
基本原理似乎是:一个已经加载的更高版本至少是可能可能 be em>向后兼容请求的较低版本 - 尽管在没有用于.net组件的语义版本的情况下 - 这是不保证的。
当不例外时做请参阅实际,已经加载的汇编版本 - 与.NET Framework不同。
powershell core 用自定义功能覆盖了此行为,与Windows PowerShell不同:
它定义了针对插件的自定义依赖处理程序
[system.reflection.sembly] :: loadfrom()
.net core api,它在以下情况下使用了场景:使用模块清单加载模块时(
.psd1
),该模块通过其essirte> quilt> essirtageAssemblies
entry来声明依赖的汇编。
呼叫 带有
-path
/-literalPath
。在
中使用汇编
语句。此自定义依赖性分辨率处理程序 明确地预防尝试加载已经加载的版本的不同版本,无论哪个版本编号更高。
带有同名的汇编已经加载
错误(在使用assembly 的情况下,在的情况下,加载脚本在解析阶段中断,带有>
无法加载汇编'...'
)。UPSHOT :
在PowerShell Core中,尝试加载已加载的汇编的不同版本总是失败,如果您让 powershell 进行负载尝试。
仅当您使用.net core的隐式依赖项加载时,偶然的机会 即可成功,即如果尝试加载a << em> lower 版本以后。隐式依赖加载会自动发生,通过 .NET API,该API基于提供依赖的汇编(位于位置独立于位置)全名和默认探测路径寻找托管 file ,它特别包括调用汇编本身的目录。
此可能会或可能不工作,鉴于.net组件的版本使用不使用 smentical 版本,因此又有更高版本的汇编版本是' t 保证向后兼容。
另外,依赖依赖的隐式加载的正时是无法预测的,通常直到所讨论的组件的类型实际上是使用使用 (构建实例,属性访问,方法调用等等)。因此,如果您依靠模块使用隐式依赖性加载,则在导入模块与何时尝试进行实际依赖性加载之间没有保证的关系,因此,如果具有a lows 版本恰好是在给定的会话中加载的第一个(但是,如前所述,即使首先加载了较高的版本,您也可能会看到以后的错误。 em>)。
tl;dr
When authoring a module, it is best to use a module manifest (
.psd1
file) with aRequiredAssemblies
key to declare dependent (helper) assemblies, because it makes loading of such assemblies predictable, because it happens at the time of module import.In Windows PowerShell, attempts to load a different version of an already-loaded assembly are quietly ignored and the newly imported module is made to use the already-loaded assembly too, which may or may not work.
In PowerShell (Core) (v6+), attempts to load a different version of an already-loaded assembly are categorically prevented.
If you use implicit loading of dependent assemblies, based on .NET's mechanisms, the timing of when these dependencies are loaded isn't guaranteed, and, in PowerShell (Core), you'll bypass the check that prevents attempts to load a different version of an already-loaded assembly - which may or may not work and is only allowed if the later load attempt requests a lower version than the one already loaded.
Non-trivial workarounds are needed to reliably load different versions of a given assembly side by side.
Background information
One of your links, Resolving PowerShell module assembly dependency conflicts, contains all relevant information, but it's a lengthy read with complex, in-depth information, and it doesn't clarify the way in which PowerShell (Core) (v6+) overrides the behavior of the underlying .NET (Core) / .NET 5+ framework.
Terminology: For simplicity, I'll use the following terms:
Windows PowerShell is the legacy, ships-with-Windows Windows-only edition of PowerShell whose latest and final version is 5.1.x.
PowerShell Core (officially now just PowerShell) is the modern, cross-platform, install-on-demand PowerShell edition, whose first (but now obsolete) version was v6 and whose current version as of this writing is v7.2.6.
The underlying problem:
At the .NET framework / CLR (Common Language Runtime) level, the fundamental problem is the inability to load different versions of a given assembly side by side into the same application domain (.NET Framework) / ALC (assembly-load context, .NET Core): the version that is loaded first into a session quietly wins, with .NET Core imposing the additional restriction that only attempts to load lower versions succeed (see below).
PowerShell uses only a single application domain / ALC, including for modules, necessitating workarounds to enable side-by-side loading of different versions of a (dependent) assembly. The linked article details various workarounds; a robust, generic, in-process workaround requires PowerShell Core and is limited to binary (compiled) modules and is nontrivial; limited workarounds, some out-of-process, are available for Windows PowerShell and cross-PowerShell-edition code.
Behavior without a workaround:
Windows PowerShell and .NET Framework:
.NET Framework quietly ignores subsequent attempts to load a different version of an already-loaded assembly, and makes the caller use the already-loaded version, irrespective of whether the requested version is higher or lower than the already loaded one.
typeof
to inspect the assembly and its types it just loaded (whether implicitly or explicitly), the requested assembly version is reported - even though it is the already loaded assembly and its types that are actually used.Windows PowerShell does not overlay this behavior with custom functionality, although whether or not you use a module manifest (
.psd1
file) with aRequiredAssemblies
entry can make a difference with respect to when dependent assemblies are loaded (see below).Upshot:
Attempts to load a different version of an already-loaded assembly never fail and quietly make the caller use the already-loaded version.
This may or may not work, and you'll only notice at the time the types from the assembly in question are actually used (construction of an instance, property access, method calls, ...).
PowerShell Core and .NET Core:
.NET Core:
Quietly ignores subsequent attempts to load a lower version of an already-loaded assembly, and makes the caller use the already-loaded version
Throws an exception for attempts to load a higher version:
Could not find or load a specific file. (0x80131621)
The rationale appears to be: an already-loaded higher version is at least likely to be backward-compatible with the requested lower version - although, in the absence of semantic versioning for .NET assemblies - this is not guaranteed.
When no exception occurs, and the caller uses reflection with
typeof
to inspect the assembly and its types it just loaded (whether implicitly or explicitly), it does see the actual, already-loaded assembly version - unlike in .NET Framework.PowerShell Core does overlay this behavior with custom functionality, unlike Windows PowerShell:
It defines a custom dependency handler for the plugin-oriented
[System.Reflection.Assembly]::LoadFrom()
.NET Core API, which it uses behind the scenes in the following contexts:When loading a module with a module manifest (
.psd1
) that declares dependent assemblies via itsRequiredAssemblies
entry.Calls to
Add-Type
with-Path
/-LiteralPath
.in
using assembly
statements.This custom dependency resolution handler categorically prevents attempts to load a different version of an already-loaded version, irrespective of which version number is higher.
Assembly with same name is already loaded
error occurs (in the case ofusing assembly
, loading a script breaks in the parsing stage withCannot load assembly '...'
).Upshot:
In PowerShell Core, attempts to load a different version of an already-loaded assembly always fail if you let PowerShell make the load attempt.
Only if you use .NET Core's implicit dependency loading is there a chance that the load attempt succeeds, namely if an attempt is made to load a lower version later. Implicit dependency loading happens automatically, via the
[System.Reflection.Assembly]::Load()
.NET API, which is based on supplying a dependent assembly's (location-independent) full name and default probing paths for looking for the file hosting it, which notably includes the directory of the calling assembly itself.This may or may not work, given that versioning of .NET assemblies doesn't use semantic versioning, so a higher version of an assembly isn't guaranteed to be backward-compatible.
Also, the timing of implicit loading of dependencies isn't predictable, and usually doesn't happen until the types from the assembly in question are actually used (construction of an instance, property access, method calls, ...). Thus, if you rely on your modules to use implicit dependency loading, there is no guaranteed relationship between when a module is imported vs. when actual dependency loading is attempted, so it may fail if a dependency with a lower version happens to be loaded first in a given session (but, as stated, even if the higher version is loaded first, you may see a later error when the types are actually used).