我發現了一個我無法完全理解的有趣案例。
我的運行時 - PowerShell 7.2.4
我有兩個 PowerShell Core 模塊:Net6PowerShellModule和NetstandardPowerShellModule.
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>
測驗-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'
}
NetstandardPowerShellModule.csproj:
<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>
測驗-NetstandardCmdlet.cs
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.");
}
}
}
NetstandardPowerShellModule.psd1
@{
RootModule = 'NetstandardPowerShellModule.dll'
ModuleVersion = '0.0.1'
GUID = 'eef615d0-aecf-4e89-8f4c-53174f8c99d6'
CmdletsToExport = 'Test-NetstandardCmdlet'
}
接下來,我對這些模塊有兩個可能的用例:
- 進口
Net6PowerShellModule - 進口
NetstandardPowerShellModule - 跑
Test-Net6Cmdlet - 跑
Test-NetstandardCmdlet
結果 - 一切運行正常。唯一加載的程式集是Microsoft.Extensions.DependencyInjection.Abstractionsversion 6.0.0。
- 進口
Net6PowerShellModule - 進口
NetstandardPowerShellModule - 跑
Test-NetstandardCmdlet - 運行
Test-Net6Cmdlet。
結果 - 錯誤:Test-Net6Cmdlet: Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. Could not find or load a specific file. (0x80131621)
基于這個問題,這不是預期的行為:
隨后嘗試加載不同版本的程式集:在 Powershell (Core) 7 中導致陳述句終止錯誤
Assembly with same name is already loaded.
為什么第一個場景會起作用?
如果我RequiredAssemblies = @('Microsoft.Extensions.DependencyInjection.Abstractions.dll')在兩個模塊清單中添加該行,我會得到一個預期的行為:無論首先加載哪個模塊,我仍然得到一個錯誤:Assembly with same name is already loaded.
更新 1
我已經看過的文章:
- https://docs.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts
- PowerShell:為什么我能夠在一個會話中加載同一個 .NET 程式集的多個版本并“繞過依賴地獄”?
- https://devblogs.microsoft.com/powershell/resolving-powershell-module-assembly-dependency-conflicts/ - 這篇文章解釋了很多關于程式集加載背景關系,但我仍然無法理解
RequiredAssemblies在檔案中定義.psd1或使用代碼中的依賴。
uj5u.com熱心網友回復:
tl;博士
撰寫模塊時,最好使用帶有鍵的模塊清單(
.psd1檔案)RequiredAssemblies來宣告依賴(幫助)程式集,因為它使此類程式集的加載可預測,因為它發生在模塊匯入時。在 Windows PowerShell 中,加載已加載程式集的不同版本的嘗試會被悄悄忽略,新匯入的模塊也會使用已加載的程式集,這可能會或可能不會作業。
在 PowerShell Core (v6 ) 中,絕對禁止嘗試加載已加載程式集的不同版本。
如果您使用基于 .NET 機制的依賴程式集的隱式加載,則無法保證加載這些依賴項的時間,并且在 PowerShell Core 中,您將繞過阻止嘗試加載不同版本的已加載的程式集 - 可能會或可能不會作業,并且僅在以后的加載嘗試請求的版本低于已加載的版本時才允許。
需要解決方法來可靠地并排加載給定程式集的不同版本。
背景資料
您的鏈接之一,解決 PowerShell 模塊程式集依賴沖突,包含所有相關資訊,但它是一個冗長的閱讀復雜、深入的資訊,并且它沒有闡明PowerShell (Core) (v6 )覆寫行為的方式底層 .NET (Core) / .NET 5 框架。
術語:為簡單起見,我將使用以下術語:
Windows PowerShell是 PowerShell 的舊版、僅 Windows Windows 版本,其最新和最終版本是 5.1.x。
- 它建立在.NET Framework之上,這是 .NET的舊版、僅限 Windows 的版本,其最新和最后一個版本是 v4.8.x
PowerShell Core(現在正式稱為PowerShell)是現代的、跨平臺的、按需安裝的 PowerShell 版本,其第一個(但現在已過時)版本是 v6,在撰寫本文時其當前版本是 v7.2.4。
- 它建立在.NET Core(正式從 v5 開始只是.NET)之上,這是 .NET 的現代、跨平臺、按需安裝版本。它建立在.NET Framework和PowerShell Core之上。
根本問題:
在 .NET 框架/CLR(公共語言運行時)級別,根本問題是無法將給定程式集的不同版本并排加載到同一應用程式域(.NET 框架)/ALC(程式集加載背景關系,. NET Core) :首先加載到會話中的版本悄悄地獲勝,.NET Core 施加了僅嘗試加載較低版本成功的附加限制(見下文)。
PowerShell 僅使用單個應用程式域/ALC,包括模塊,需要變通方法以支持并行加載(依賴)程式集的不同版本。鏈接的文章詳細介紹了各種解決方法;一個健壯的、通用的、行程內的解決方法需要 PowerShell Core 并且非常重要;對于 Windows PowerShell 和跨 PowerShell 版本的代碼,有限的變通方法(一些行程外)可用。
沒有解決方法的行為:
Windows PowerShell 和 .NET 框架:
.NET Framework 悄悄地忽略后續嘗試加載已加載程式集的不同版本的嘗試,并讓呼叫者使用已加載版本,而不管請求的版本是高于還是低于已加載的版本。
- 奇怪的是,當呼叫者使用反射來
typeof檢查它剛剛加載的程式集及其型別(無論是隱式還是顯式)時,會報告請求的程式集版本——即使它是已經加載的程式集及其實際使用的型別。
- 奇怪的是,當呼叫者使用反射來
與 .NET Core 不同, Windows PowerShell 不會使用自定義功能覆寫此行為,盡管是否使用帶有條目的模塊清單(
.psd1檔案)可能會在加載依賴程式集時有所不同(見下文)RequiredAssemblies結果:
嘗試加載已加載程式集的不同版本永遠不會失敗,并且會悄悄地使呼叫者使用已加載的版本。
這可能有效,也可能無效,您只會在實際使用相關程式集的型別時注意到(實體的構造、屬性訪問、方法呼叫……)。
- 值得注意的是,如果后來的呼叫者嘗試加載更高版本的程式集,它可能會失敗,因為它可能依賴于已經加載的低版本沒有的功能,但即使是相反的情況也不能保證成功.
PowerShell 核心和 .NET 核心:
.NET 核心:
安靜地忽略后續加載已加載程式集的較低版本的嘗試,并使呼叫者使用已加載的版本
嘗試加載更高版本時拋出例外:
Could not find or load a specific file. (0x80131621)基本原理似乎是:已加載的較高版本至少可能與請求的較低版本向后兼容- 盡管在沒有 .NET 程式集的語意版本控制的情況下 - 這不能保證。
如果沒有發生例外,并且呼叫者使用反射來
typeof檢查它剛剛加載的程式集及其型別(無論是隱式還是顯式),它確實會看到實際的、已加載的程式集版本 - 與 .NET Framework 不同。
與 Windows PowerShell 不同,PowerShell Core確實使用自定義功能覆寫了此行為:
它為面向插件的
[System.Reflection.Assembly]::LoadFrom().NET Core API 定義了一個自定義依賴處理程式,它在以下背景關系中在幕后使用它:加載帶有模塊清單 ( ) 的模塊時,該模塊清單 () 通過其條目
.psd1宣告依賴程式集。RequiredAssembliesAdd-Type用-Path/呼叫-LiteralPath。在
using assembly宣告中。
此自定義依賴關系決議處理程式絕對阻止嘗試加載已加載版本的不同版本,無論哪個版本號更高。
- 發生陳述句終止
Assembly with same name is already loaded錯誤(在 的情況下using assembly,在決議階段加載腳本會中斷Cannot load assembly '...')。
- 發生陳述句終止
結果:
在 PowerShell Core 中,如果您讓PowerShell進行加載嘗試,則嘗試加載已加載程式集的不同版本總是會失敗。
僅當您使用 .NET Core 的隱式依賴加載時,加載嘗試才有可能成功,即稍后嘗試加載較低版本。隱式依賴加載通過
[System.Reflection.Assembly]::Load().NET API 自動發生,該 API 基于提供依賴程式集的(與位置無關的)全名和用于查找托管它的檔案的默認探測路徑,其中特別包括呼叫程式集本身的目錄。這可能會也可能不會起作用,因為 .NET 程式集的版本控制不使用語意版本控制,因此不能保證更高版本的程式集向后兼容。
此外,隱式加載依賴項的時間是不可預測的,并且通常在實際使用相關程式集中的型別(實體的構造、屬性訪問、方法呼叫......)之前不會發生。因此,如果您依賴模塊使用隱式依賴加載,則在匯入模塊與嘗試實際加載依賴之間沒有保證關系,因此如果碰巧先加載了較低版本的依賴,則可能會失敗在給定的會話中(但是,如前所述,即使先加載更高版本,當實際使用型別時,您也可能會看到稍后的錯誤)。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/487109.html
