Repository navigation
PowerShell included modules does not have a version directory like Windows PowerShell had, causing assembly conflict on module import #21199
Description
Activity
- addedNeeds-TriageThe issue is new and needs to be triaged by a work group.The issue is new and needs to be triaged by a work group.
on Feb 8, 2024 Is this intentional? Can this cause the issue I experience with PSResourceGet?
Have a look at $env:PSModulePath, for example mine is
D:\OneDrive\Documents\PowerShell\Modules;C:\Program Files\PowerShell\Modules;c:\program files\powershell\7\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\ModulesThe directories that you listed at the ones that are part of the MSI install and should be static or managed by the MSI and updated with a new MSI. I suggest you should not be overwriting those.
Your installed modules should go in, say in my case
D:\OneDrive\Documents\PowerShell\Modules. Here install-module does give them the module-name/version directory structure.D:\> tree D:\OneDrive\Documents\PowerShell\Modules Folder PATH listing for volume data512 Volume serial number is 3EE9-A4CC D:\ONEDRIVE\DOCUMENTS\POWERSHELL\MODULES ├───rhubarb-geek-nz.CancellationTokenEvent │ └───1.0.1 ├───rhubarb-geek-nz.Forever │ └───0.0.2 ├───rhubarb-geek-nz.MySqlConnection │ └───2.3.1 ├───rhubarb-geek-nz.NpgsqlConnection │ └───7.0.6 ├───rhubarb-geek-nz.OracleConnection.Core │ └───3.21.120 ├───rhubarb-geek-nz.PowerShellDataFile │ └───1.0.0 ├───rhubarb-geek-nz.SqlConnection │ └───1.0.4 └───rhubarb-geek-nz.SQLiteConnection └───1.0.118.0 ├───linux-arm ├───linux-arm64 ├───linux-x64 ├───osx-arm64 ├───osx-x64 ├───win-arm ├───win-arm64 ├───win-x64 └───win-x86Sure. Here's my scenario though:
- If installing modules in user context/scope and you have OneDrive for Business (ODfB) Known Folder Move (KFM) enabled,
PSResourceGet,PowerShellGetandPackageManagementwill install modules to%OneDrive%\Documents\PowerShell\Modules%(due to the folder redirection by ODfB KFM), and thus sync thousands of small files to OneDrive. - I don't want this behavior, so I've made a directory
%LOCALAPPDATA%\Microsoft\PowerShell\Moduleswhere I install modules usingSave-PSResourceasInstall-PSResourcedoes not care about$env:PSModulePathand instead uses hardcoded paths for user and system context.
I end up with:
Module Version Path Microsoft.PowerShell.PSResourceGetv1.0.1 %ProgramW6432%\PowerShell\7\Modules\Microsoft.PowerShell.PSResourceGet\<content>Microsoft.PowerShell.PSResourceGetv1.0.2 %LOCALAPPDATA%\Microsoft\PowerShell\Modules\Microsoft.PowerShell.PSResourceGet\1.0.2\<content>Starting a fresh pwsh.exe v7.4.1 window gives me the following:
PowerShell 7.4.1 PS C:\Users\olavb> [System.Environment]::GetEnvironmentVariable('PSModulePath','Process').Split(';') C:\Users\olavb\Documents\PowerShell\Modules C:\Program Files\PowerShell\Modules c:\program files\powershell\7\Modules C:\Users\olavb\AppData\Local\Microsoft\PowerShell\Modules C:\Program Files\WindowsPowerShell\Modules C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules PS C:\Users\olavb> [System.Environment]::GetEnvironmentVariable('PSModulePath','User').Split(';') C:\Users\olavb\AppData\Local\Microsoft\PowerShell\Modules PS C:\Users\olavb> [System.Environment]::GetEnvironmentVariable('PSModulePath','Machine').Split(';') C:\Program Files\WindowsPowerShell\Modules C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules PS C:\Users\olavb>
And
PS C:\Users\olavb> Get-Module -ListAvailable 'Microsoft.PowerShell.PSResourceGet' | Format-List -Property 'Name','Version','Path' Name : Microsoft.PowerShell.PSResourceGet Version : 1.0.1 Path : C:\program files\powershell\7\Modules\Microsoft.PowerShell.PSResourceGet\Microsoft.PowerShell.PSResourceGet.psd1 Name : Microsoft.PowerShell.PSResourceGet Version : 1.0.2 Path : C:\Users\olavb\AppData\Local\Microsoft\PowerShell\Modules\Microsoft.PowerShell.PSResourceGet\1.0.2\Microsoft.PowerShell.PSResourceGet.psd1 PS C:\Users\olavb>
Then, importing v1.0.2 gives:
PS C:\Users\olavb> Import-Module -Name 'Microsoft.PowerShell.PSResourceGet' -RequiredVersion 1.0.2 Import-Module: Assembly with same name is already loaded PS C:\Users\olavb>
This indicates to me that something outside of my preferences and settings are having issues. What it is I don't know.
- If installing modules in user context/scope and you have OneDrive for Business (ODfB) Known Folder Move (KFM) enabled,
This indicates to me that something outside of my preferences and settings are having issues. What it is I don't know.
It may be a chicken-and-egg problem with PSResourceGet where that is loaded early on before it starts recognizing PSModulePath.
Can you do show the same scenario with a different module?
Yes, actually. v7.4.1 also got
Microsoft.PowerShell.ThreadJobv2.0.3. Only that it's in directoryThreadJob:
If renaming that directory to
Microsoft.PowerShell.ThreadJob, and renaming the psd1 file too, we get the following (with a newer version installed outside this dir):PS C:\Users\olavb> Get-Module -ListAvailable 'Microsoft.PowerShell.ThreadJob' | Format-List -Property 'Name','Version','Path' Name : Microsoft.PowerShell.ThreadJob Version : 2.0.3 Path : C:\program files\powershell\7\Modules\Microsoft.PowerShell.ThreadJob\Microsoft.PowerShell.ThreadJob.psd1 Name : Microsoft.PowerShell.ThreadJob Version : 2.1.0 Path : C:\Users\olavb\AppData\Local\Microsoft\PowerShell\Modules\Microsoft.PowerShell.ThreadJob\2.1.0\Microsoft.PowerShell.ThreadJob.psd1 PS C:\Users\olavb>
And voila:
PS C:\Users\olavb> Import-Module -Name 'Microsoft.PowerShell.ThreadJob' -RequiredVersion '2.1.0' Import-Module: Assembly with same name is already loaded PS C:\Users\olavb>
Ergo:
- This is module independent.
- If adding a
<version>directory for the PowerShell included/embedded modules, import works as expected.PS C:\Users\olavb> Get-Module -ListAvailable 'Microsoft.PowerShell.ThreadJob' | Format-List -Property 'Name','Version','Path' Name : Microsoft.PowerShell.ThreadJob Version : 2.0.3 Path : C:\program files\powershell\7\Modules\Microsoft.PowerShell.ThreadJob\2.0.3\Microsoft.PowerShell.ThreadJob.psd1 Name : Microsoft.PowerShell.ThreadJob Version : 2.1.0 Path : C:\Users\olavb\AppData\Local\Microsoft\PowerShell\Modules\Microsoft.PowerShell.ThreadJob\2.1.0\Microsoft.PowerShell.ThreadJob.psd1 PS C:\Users\olavb> Import-Module -Name 'Microsoft.PowerShell.ThreadJob' -RequiredVersion '2.1.0' PS C:\Users\olavb>
- Thus this GitHub issue is still valid as far as I understand.
In your path
C:\Users\olavb\Documents\PowerShell\Modules C:\Program Files\PowerShell\Modules c:\program files\powershell\7\Modules C:\Users\olavb\AppData\Local\Microsoft\PowerShell\ModulesYour 1.0.2 in
AppData\Localis after 1.0.1 inpowershell\7in the path sequence.So it finds 1.0.1 before 1.0.2
If 1.0.2 was in
C:\Users\olavb\Documents\PowerShell\Modulesthen does it find it?I don't know how
$env:PSModulePathin process context is stitched together.If I set my preferred module path first; yes, that also works.
PowerShell 7.4.1 PS C:\Users\olavb> $env:PSModulePath.Split(';') C:\Users\olavb\Documents\PowerShell\Modules C:\Program Files\PowerShell\Modules c:\program files\powershell\7\Modules C:\Users\olavb\AppData\Local\Microsoft\PowerShell\Modules C:\Program Files\WindowsPowerShell\Modules C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules PS C:\Users\olavb> [System.Environment]::SetEnvironmentVariable( >> 'PSModulePath', >> ( >> $( >> [string[]]( >> [System.Environment]::GetEnvironmentVariable('PSModulePath','User'), >> [System.IO.Path]::Combine($PSHOME,'Modules') >> ) -join [System.IO.Path]::PathSeparator >> ) >> ), >> 'Process' >> ) PS C:\Users\olavb> $env:PSModulePath.Split(';') C:\Users\olavb\AppData\Local\Microsoft\PowerShell\Modules C:\Program Files\PowerShell\7\Modules PS C:\Users\olavb> Get-Module -ListAvailable 'Microsoft.PowerShell.PSResourceGet' | Format-List -Property 'Name','Version','Path' Name : Microsoft.PowerShell.PSResourceGet Version : 1.0.2 Path : C:\Users\olavb\AppData\Local\Microsoft\PowerShell\Modules\Microsoft.PowerShell.PSResourceGet\1.0.2\Microsoft. PowerShell.PSResourceGet.psd1 Name : Microsoft.PowerShell.PSResourceGet Version : 1.0.1 Path : C:\Program Files\PowerShell\7\Modules\Microsoft.PowerShell.PSResourceGet\Microsoft.PowerShell.PSResourceGet.p sd1 PS C:\Users\olavb> Import-Module -Name 'Microsoft.PowerShell.PSResourceGet' -RequiredVersion 1.0.2 PS C:\Users\olavb>
So yes, adding a profile script to change the order of PSModulePath could be a workaround. But it shouldn't be neccessary for basic functionality one expects to work.
Reacted by rhubarb-geek-nzSo yes, adding a profile script to change the order of PSModulePath could be a workaround. But it shouldn't be neccessary for basic functionality one expects to work.
That is consistent with how paths normally work. I could have five different versions of pwsh.exe on my system, but the one that is found first in PATH is the one that will be run.
Now you could argue that it should read all the entries in all the module path directories and then select the one with the highest version. That would be sensible but currently I don't think that is implemented.
Reacted by Olav Rønnestad BirkelandThat is consistent with how paths normally work. I could have five different versions of pwsh.exe on my system, but the one that is found first in PATH is the one that will be run.
Except that it works as expected if you add a
<version>directory to the PowerShell embedded modules.Example:
- Before:
%ProgramW6432%\PowerShell\7\Modules\Microsoft.PowerShell.PSResourceGet\<content> - After:
%ProgramW6432%\PowerShell\7\Modules\Microsoft.PowerShell.PSResourceGet\1.0.1\<content>
So there's currently two workarounds I know of:
- Add embedded module to a
<version>directory- Before:
%ProgramW6432%\PowerShell\7\Modules\<modulename>\<content> - After:
%ProgramW6432%\PowerShell\7\Modules\<modulename>\<version>\<content>
- Before:
- Change order of PSModulePath paths in process context to have directory with newest version of module first, then import.
- Before:
You can set PSModulePath in your environment before you run pwsh.exe if that helps.
I would avoid changing the contents of MSI installed directories. If you upgrade you will get the version-less directory back again.
You can set PSModulePath in your environment before you run pwsh.exe if that helps.
Yes, which makes this a bug with PowerShell IMO.
I would avoid changing the contents of MSI installed directories. If you upgrade you will get the version-less directory back again.
I agree. This was more of a proof that theres a breaking change between how Windows PowerShell included modules vs. how PowerShell does it. :)
Reacted by rhubarb-geek-nzAFAIK There are three types of directories included in
PSModulePathScope WinPS Path Pwsh Path System C:\Windows\System32\WindowsPowerShell\v1.0\Modules C:\Program Files\PowerShell\7\Modules AllUsers C:\Program Files\WindowsPowerShell\Modules C:\Program Files\PowerShell\Modules CurrentUser C:\Users$username\Documents\WindowsPowerShell\Modules C:\Users$username\Documents\PowerShell\Modules PowerShell 7 muddies the water a bit as it also pulls in the System and AllUsers from WinPS but that's more for backwards compatibility support.
The
Systemscope is modules bundled specifically with the PowerShell install. You aren't meant to touch these as it's what PowerShell technically ships with. This does not have version specific directories as it's only ever meant to contain 1 version of the module. TheAllUsersandCurrentUserscopes are whatInstall-Module,Install-PSResourcecan manage and contains versioned module entries.While you aren't meant to touch the
Systemones it seems like there might be an assembly conflict happening here stopping you from loading the newer version. I’ll have to take a closer look to see what that is and why PowerShell is still loading the existing module when a newer one should theoretically be preferred.Reacted by Olav Rønnestad BirkelandI've just done a test, you can install the version side by side but as the module loads a binary and .NET only allows one version of the same binary to be loaded it will fail when you attempt to import a newer version in the same process. This is only the case when trying to import straight after upgrading. Starting a fresh new process works just fine though as it will favour the new version.
PowerShell 7.4.1 PS C:\Users\vagrant-domain> Import-module Microsoft.PowerShell.PSResourceGet -PassThru ModuleType Version PreRelease Name ExportedCommands ---------- ------- ---------- ---- ---------------- Binary 1.0.1 Microsoft.PowerShell.PSResourceGet {Find-PSResource, Get-InstalledPSResource, Get-PS… PS C:\Users\vagrant-domain> Install-module -Name Microsoft.PowerShell.PSResourceGet -Scope AllUsers -Force PS C:\Users\vagrant-domain> Import-module Microsoft.PowerShell.PSResourceGet -PassThru Import-Module: Assembly with same name is already loaded PS C:\Users\vagrant-domain> pwsh { Import-module Microsoft.PowerShell.PSResourceGet -PassThru } ModuleType Version PreRelease Name ExportedCommands ---------- ------- ---------- ---- ---------------- Binary 1.0.2 Microsoft.PowerShell.PSResourceGet {Find-PSResource, Get-InstalledPSResource, Get-PS…
TLDR: You can have newer versions that are favoured over the package provided ones, you just can't load multiple versions in the same process.
Try to change PSModulePath in process context to have the path to the packaged version first Jordan Borean (@jborean93), then try importing the newest version. Then loading the newest version fails, even in a fresh session, even is specifying
-RequiredVersion. Somehow the packaged assembly gets loaded in the process.I use a non-standard path because of OneDrive for Business KFM. Then my path, even though I've added it to PSModulePath in user context, gets added as one if the lasts paths in PSModulePath in process context. Related issue:
Edit: One more way to mitigate this; override path in
Import-Module -Namelike so:Import-Module -Name ('{0}\Microsoft\PowerShell\Modules\Microsoft.PowerShell.PSResourceGet' -f $env:LOCALAPPDATA) -RequiredVersion '1.0.2'
If I understand this correctly: Generation of PSModulePath does not work as documented here:
Documentation says it will add PSModulePath in user context first if it exists. Which it does for me. Still
$PSHOMEgets prioritized over it.PowerShell 7.4.1 PS C:\Users\olav.birkeland> [System.Environment]::GetEnvironmentVariable('PSModulePath','User').Split(';') %LOCALAPPDATA%\Microsoft\PowerShell\Modules PS C:\Users\olav.birkeland> [System.Environment]::GetEnvironmentVariable('PSModulePath','Process').Split(';') C:\Users\olav.birkeland\OneDrive - <my_employer>\Documents\PowerShell\Modules C:\Program Files\PowerShell\Modules c:\program files\powershell\7\Modules C:\Users\olav.birkeland\AppData\Local\Microsoft\PowerShell\Modules C:\Program Files\WindowsPowerShell\Modules C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules PS C:\Users\olav.birkeland>
While Windows PowerShell functions as I'd expect in this regard.
PS C:\Users\olav.birkeland> [System.Environment]::GetEnvironmentVariable('PSModulePath','Process').Split(';') C:\Users\olav.birkeland\AppData\Local\Microsoft\PowerShell\Modules C:\Program Files\WindowsPowerShell\Modules C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules PS C:\Users\olav.birkeland> [System.Environment]::GetEnvironmentVariable('PSModulePath','User').Split(';') %LOCALAPPDATA%\Microsoft\PowerShell\Modules PS C:\Users\olav.birkeland> $psVersionTable Name Value ---- ----- PSVersion 5.1.22621.2506 PSEdition Desktop PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...} BuildVersion 10.0.22621.2506 CLRVersion 4.0.30319.42000 WSManStackVersion 3.0 PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1 PS C:\Users\olav.birkeland>
If PSModulePath in user context was prioritized over
$PSHOME\Modulesloading newer versions of PSResourceGet and ThreadJob would work as expected.Seems PowerShell is hardcoded to prioritize built in modules over PSModulePath:
PowerShell/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs
Line 2021 in 0919240
// If it is one of built-in modules, first try loading it from $PSHOME\Modules, otherwise rely on $env:PSModulePath. - changed the title
[-]PowerShell included modules does not have a version directory like Windows PowerShell had[/-][+]PowerShell included modules does not have a version directory like Windows PowerShell had, causing assembly conflic on import[/+]on Feb 28, 2026 - changed the title
[-]PowerShell included modules does not have a version directory like Windows PowerShell had, causing assembly conflic on import[/-][+]PowerShell included modules does not have a version directory like Windows PowerShell had, causing assembly conflic on module import[/+]on Feb 28, 2026 o-l-a-v commented
on Feb 28, 2026 AuthorMore actionsTwo years have gone by.
Why can't included modules just be added to a versioned directory so we don't have to deal with this?
I created this quick and dirty script to do this after installation:
Click to expand
<# .NOTES Author: Olav Rønnestad Birkeland | github.com/o-l-a-v Created: 2026-02-28 Modified: 2026-02-28 It seems that when `Import-Module` searches through `$env:PSModulePath` for available modules to import, and stumbles across a module that is not contained in a directory consisting of its' version number, it will lazily import assemblies and thus block importing of any other version of the same module. * Triggers this: `Modules/<module_name>/<module_name>.psd1` * Does not trigger this: `Modules/<module_name>/<module_version>/<module_name>.psd1` Related: * 2025-02-08: <https://github.com/PowerShell/PowerShell/issues/21201> * 2024-02-08: <https://github.com/PowerShell/PowerShell/issues/21199> Thoughts on how to fix: Put included modules in a versioned directory. * Do not use `Get-Module` to find the module version as it might lazy load the module, which will block moving it. * Thus parse "<module_name>.psd1" manually. .EXAMPLE . $psEditor.GetEditorContext().CurrentFile.Path -ModulesDirectory ( 'C:\Users\olavb\scoop\apps\pwshp\7.6.0-rc.1\Modules' ) -WhatIf #> # Input and expected output [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Void])] Param( [Parameter(Mandatory)] [ValidateNotNullOrWhiteSpace()] [ValidateScript({[System.IO.Directory]::Exists($_)})] $ModulesDirectory ) # PowerShell preferences $ErrorActionPreference = 'Stop' $InformationPreference = 'Continue' # Find info $Modules = [array](Get-ChildItem -Path $ModulesDirectory -Directory) $ModulesDetails = [PSCustomObject[]]( $Modules.ForEach{ [PSCustomObject]@{ 'Name' = [string] $_.'Name' 'Path' = [string] $_.'FullName' 'Psd1' = [System.IO.Path]::Combine($_.'FullName', [string]::Format('{0}.psd1' -f $_.'Name')) } }.ForEach{ Add-Member -InputObject $_ -PassThru -NotePropertyMembers @{ 'Psd1Exists' = [bool]([System.IO.File]::Exists($_.'Psd1')) } } | Sort-Object -Property 'Name' ) # Move modules into a versioned directory foreach ($Module in $ModulesDetails) { # Output current module Write-Information -MessageData ( '{0}# {1}' -f $(if($ModulesDetails.IndexOf($Module -ne 0)){[System.Environment]::NewLine}), $Module.'Name' ) # Failproof: Make sure Psd1 file exists at expected path if (-not $Module.'Psd1Exists') { Write-Information -MessageData ( '"{0}" was not found, has probably been processed already.' -f $Module.'Psd1' ) Continue } # Get version from Psd1 file $Version = [System.Version]( ( [System.IO.File]::ReadAllLines($Module.'Psd1').Where( { -not [string]::IsNullOrWhiteSpace($_) -and $_.Trim() -match '^ModuleVersion\s{0,}=' }, 'First' )[0] -as [string] -replace '\s{1,}' -replace '''', '"' ).Split('"')[1] ) # Failproof: Don't continue if version could not be parsed if ([string]::IsNullOrWhiteSpace($Version)) { Write-Information -MessageData 'Failed to find version.' Continue } # Generate new path $NewPath = [System.IO.Path]::Combine($Module.'Path', $Version) # Find items to be moved $ItemsToMove = [string[]]( (Get-ChildItem -Path $Module.'Path').'FullName' ) if ($PSCmdlet.ShouldProcess($NewPath, 'Move')) { $null = [System.IO.Directory]::CreateDirectory($NewPath) $ItemsToMove.ForEach{ $null = Move-Item -Path $_ -Destination $NewPath } Write-Information -MessageData ('Successfully moved files into "{0}".' -f $NewPath) } }
- changed the title
[-]PowerShell included modules does not have a version directory like Windows PowerShell had, causing assembly conflic on module import[/-][+]PowerShell included modules does not have a version directory like Windows PowerShell had, causing assembly conflict on module import[/+]on Feb 28, 2026 Why can't included modules just be added to a versioned directory
This has been previously discussed in #2350
Until the module is decoupled from PSCore itself, there's no value to have it in a versioned folder since it's not updatable outside of updating PSCore itself. The ones that have versioned folders are those obtained from PSGallery.
This is by design for maintenance and supportability reasons
o-l-a-v commented
on Jun 23, 2026 AuthorMore actionsRyan Yates (@kilasuit): I guess the fix is to fix the installed modules version lookup to not lazy load assemblies then, or whatever it does. Maybe it could do that discovery in a child process?
It seems to me that PowerShell is using a brute force module discovery mechanism that works for looking for executables on a PATH but not modules in nested subtrees. I suggest the problem is the promise that you can just add a module by copying into a directory on the path and PowerShell will eventually find it and use it.
Does PowerShell manage a persistent index for the modules it knows about?
If PowerShell maintained an index in a simple database where manifest files were added and removed by Install-Module/Install-PSResource/Uninstall-Module/Uninstall-PSResource etc then cmdlets etc would be found by referring to the manifests in the index rather than exhaustive searches in the file system.
Prerequisites
Steps to reproduce
Issue
I recently experienced a problem with importing a newer version of Microsoft.PowerShell.PSResourceGet (v1.0.2) than what is included with PowerShell 7.4.1 (v1.0.1):
I started researching and noticed that:
%ProgramW6432%\WindowsPowerShell\Modules\<modulename>\<version>\<content><installpath>\<modulename>\<version>\<content>.%ProgramW6432%\PowerShell\7\Modules\<modulename>\<content>Is this intentional? Can this cause the issue I experience with PSResourceGet?
My understanding of the issue
It seems that when
Import-Modulesearches through$env:PSModulePathfor available modules to import, and stumbles across a module that is not contained in a directory consisting of its' version number, it will lazily import assemblies and thus block importing of any other version of the same module.Ways to mitigate this
Three different ways to get
Import-Module -Name 'Microsoft.PowerShell.PSResourceGet' -Version 1.0.2to work.%ProgramW6432%\PowerShell\7\Modules\Microsoft.PowerShell.PSResourceGet%ProgramW6432%\PowerShell\7\Modules\Microsoft.PowerShell.PSResourceGet\*%ProgramW6432%\PowerShell\7\Modules\Microsoft.PowerShell.PSResourceGet\1.0.1\*$env:PSModulePathto have the path with the newest version of the module first.Expected behavior
Actual behavior
Error details
See: #21201
Environment data
Visuals
No response