I'm working in Windows 10 x64, PowerShell version 5.1.
I've got multiple versions of a .NET Framework 4.0 assembly (.dll) written in C# (by myself). Assemblies are not signed. Their versions are set in AssemblyInfo.cs
via [assembly: AssemblyVersion("X.X.X.X")]
. The [assembly: AssemblyFileVersion("X.X.X.X")]
tag does not exist in my AssemblyInfo.cs
! These assemblies are used in a regular .NET Framework application, they're not specialized PowerShell modules nor they have manifest files.
I use this assembly in some PowerShell scripts to create objects and call methods from it.
I've got two versions of this assembly, let's say 1.1.1.100
and 1.2.1.100
. When I import let's say 1.1.1.100
version in PowerShell via Import-Module "D:\path\to_v1.1\MyAssembly.dll"
, it works just fine. When I call Get-Module
I see this imported assembly in the list:
ModuleType Version Name
---------- ------- ----
Binary 1.1.1.100 MyAssembly
Now the things start to get interesting. I import the other version of the assembly Import-Module "D:\another\path\to_v1.2\MyAssembly.dll"
. It again works just fine. So here're my questions:
Q1. Why are no errors shown? I expect to get an error: I'm trying to load an assembly with the same name but different version. I thought you could not load two different versions of the same assembly in one context. How are these situations handled, which loading contexts are used? Maybe the second version is not loaded at all?
Q2. Get-Module
now shows two modules with the same name and version, like this:
ModuleType Version Name
---------- ------- ----
Binary 1.1.1.100 MyAssembly
Binary 1.1.1.100 MyAssembly
It doesn't matter which version I import first. On the second Import-Module
the earliest imported version is duplicated in the Get-Module
list. I can only use the types/methods from the earliest imported version of the assembly as well.
Q3. My assembly has some dependencies on my other assemblies (in the same folder). These dependencies are automatically resolved and implicitly "imported" to the current session (I can use types from them no problem). Each version of the main MyAssembly.dll
depends on different versions of these secondary assemblies. When I import another version of MyAssembly
, I get no error about conflicting versions either. Once again, I can only use the types from the earliest imported secondary assemblies. I've read about "Dependency hell" and this should be impossible -- so how is that possible?
I've performed the same experiment with NuGet package Microsoft.CodeAnalysis.CSharp.dll
, versions 3.4.0
and 3.11.0
. The results are the same: versions and their dependencies are imported no problem, but only the earliest imported version is available.
When I import different versions of the same assemblies, I get no errors, but only the earliest imported versions are available. I want to understand why I'm able to load multiple versions of an assembly in one session without errors, how PowerShell handles these situations in general and why it shows two modules with the same versions, why I don't get dependency hell.
I want to understand what's going on, not just "make it work".
What am I missing here?
Thanks!
In your scenario, your modules are stand-alone .NET assemblies that implement PowerShell cmdlets.
In both Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and last version is 5.1) and PowerShell (Core) 7 (the modern, cross-platform, install-on-demand edition), all assemblies are loaded into the same:
application domain in Windows PowerShell, which is based on .NET Framework.
ALC (assembly-load context) in PowerShell (Core) 7, which is based on .NET Core / .NET 5+.
By default, loading multiple version of the same assembly is not supported, and the first one that is loaded into the session wins.
Subsequent attempts to load a different version of the assembly:
are quietly ignored in Windows PowerShell - irrespective of whether you use Import-Module
, Add-Type -LiteralPath
or its underlying .NET API method, [System.Reflection.Assembly]::LoadFrom()
(be sure to pass a full file path to the latter, because PowerShell's working dir. usually differs from .NET's; the method outputs an object representing the assembly ostensibly just loaded, but it it actually represents the originally loaded version).
cause a statement-terminating error in Powershell (Core) 7+
Assembly with same name is already loaded.
Note: You can load an assembly with a different version, namely via the [System.Reflection.Assembly]::LoadFile()
method (note the File
instead of From
), but the only way you can use its types is via reflection.
From what I can tell:
There is no solution for cmdlet-implementing assemblies, as in your case.
By cmdlet-implementing I mean that the module's cmdlets are themselves implemented as .NET classes stored in an assembly, as opposed to cmdlets implemented in PowerShell code, in *.psm1
script-module files.
Whichever version of the cmdlet-implementing assembly you happen to import / load first in your session is the only one available in the remainder of the session; assemblies, once loaded, cannot be unloaded.
Another way of putting it: while you can have side-by-side versions of such modules on disk, you cannot load them simultaneously into a session, from what I can tell - this only works with script-based modules (see next point).
There are - nontrivial - solutions for loading multiple versions of helper assemblies into the same session; these are detailed in Resolving PowerShell module assembly dependency conflicts, which also provided extensive background information.