I have found an interesting case which I can't fully understand.
My runtime - PowerShell 7.2.4
I have two PowerShell Core modules: Net6PowerShellModule
and 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>
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'
}
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>
Test-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'
}
Next, I have two possible use cases for those modules:
- Import
Net6PowerShellModule
- Import
NetstandardPowerShellModule
- Run
Test-Net6Cmdlet
- Run
Test-NetstandardCmdlet
Result - everything runs correctly. The only loaded assembly is Microsoft.Extensions.DependencyInjection.Abstractions
version 6.0.0
.
- Import
Net6PowerShellModule
- Import
NetstandardPowerShellModule
- Run
Test-NetstandardCmdlet
- Run
Test-Net6Cmdlet
.
Result - error: 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)
Based on this question, this is not an expected behavior:
Subsequent attempts to load a different version of the assembly: cause a statement-terminating error in Powershell (Core) 7+
Assembly with same name is already loaded.
Why does the first scenario work then?
If I add the RequiredAssemblies = @('Microsoft.Extensions.DependencyInjection.Abstractions.dll')
line in both module manifests, I got an expected behavior: regardless of which module is loaded first, I still got an error: Assembly with same name is already loaded.
Update 1
Articles that I've already looked at:
- https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts
- PowerShell: why am I able to load multiple versions of the same .NET assembly in one session and also "bypass Dependency hell"?
- https://devblogs.microsoft.com/powershell/resolving-powershell-module-assembly-dependency-conflicts/ - this article explains a lot about Assembly Load Contexts, but I still cannot understand the difference between having
RequiredAssemblies
defined in.psd1
files or using the dependencies in code.