New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to load multiple versions of .Net assembly module dependencies. #2083

Open
curiousdev opened this Issue Aug 26, 2016 · 25 comments

Comments

@curiousdev
Copy link

curiousdev commented Aug 26, 2016

I'm happy to move this to a feature-request, though I'm not 100% sure. Please advise. We've observed that when we mix 3rd-party module imports from ISV's, it's quite common that we hit assembly version collisions.

A very common example:

  • Module1 depends on Newton.Json, Version 6.0
  • Module2 depends on Newton.Json, Version 9.0

We can't use both these modules in the same session. If we import Module1, then Module2, the cmdlets from Module2 throw claiming assembly version conflicts with Newton.Json. Our current work around is to kill the powershell process and start over with the other module import.

Should powershell isolate module imports into their own AppDomains? I'm happy to implement a repo, if this is not already filed.

@rkeithhill

This comment has been minimized.

Copy link
Contributor

rkeithhill commented Aug 26, 2016

IIRC AppDomains are not supported in .NET Core.

@curiousdev

This comment has been minimized.

Copy link

curiousdev commented Aug 26, 2016

Even if AppDomains are not supported in .NET core, the issue still remains. How can powershell support multiple versions of dlls imported in the same powershell process? I can't update 3rd party modules to use newer dlls versions. So I'm stuck having to explicitly know that I can never use Module1 and Module2 cmdlets in the same ps-session.

@lzybkr lzybkr changed the title Unable to load multiple versions of module dependencies. Unable to load multiple versions of .Net assembly module dependencies. Aug 27, 2016

@SteveL-MSFT SteveL-MSFT added this to the post-6.0.0 milestone Oct 28, 2016

@SteveL-MSFT SteveL-MSFT modified the milestones: 6.1.0, post-6.0.0 Mar 4, 2017

@gogbg

This comment has been minimized.

Copy link

gogbg commented Jan 2, 2018

Any progress on this?

I have the same issue.
I have a "JsonSchemaValidation" module build by myself which should use:
Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed

At the same time AzureRm.Profile uses:
Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed

PowerShell always uses the "newtonsoft.Json" dll from the module that is being imported first!
Looking in the appdomain, both DLLs are present.
In case of AzureRm.Profile is being imported first, my "JsonSchemaValidation" cmdlets does not work.

@iSazonov

This comment has been minimized.

Copy link
Collaborator

iSazonov commented Jan 2, 2018

@gogbg You could try to use bindingRedirect.

Also it is expected that AppDomain will come back in .Net Core 2.1.0.

@xenalite

This comment has been minimized.

Copy link

xenalite commented Feb 1, 2018

I have the same issue.
My binary module has a dependency on assembly version 8 (JSON.NET).
I use it together with another module, which has a dependency on same assembly version 6.

As soon as I Import-Module both of them, 2 different versions of the same assembly are loaded into the AppDomain.

This normally wouldn't present an issue, but my binary module uses features only available in version 8 of dependent assembly. This throws MissingMethodException.

@iSazonov
I tried to handle AssemblyResolve event on the current AppDomain. I redirect any previous version of dependent assembly to v8. Unfortunately, this is too late. This event handler kicks in when I run the first cmdlet from my module. At that stage, the current AppDomain already contains v6.
This would not happen in a normal executable, as assembly redirects or assembly resolve handler would kick in when application attempts to load v6.

@SteveL-MSFT
Is this issue still being looked at? It makes some of the modules unusable together in the same session.

@iSazonov

This comment has been minimized.

Copy link
Collaborator

iSazonov commented Feb 1, 2018

It certainly does not solve this problem in general but If we talk specifically about Newton.Json then we might consider replacing it with System.Json.

@SteveL-MSFT

This comment has been minimized.

Copy link
Member

SteveL-MSFT commented Feb 1, 2018

@xenalite yes, this is an issue we are aware of and have had internal discussions about it. @daxian-dbw perhaps for now we can document some workarounds until we can address this in code

@astral-keks

This comment has been minimized.

Copy link

astral-keks commented Feb 27, 2018

Hello everyone!
BTW it also seems that PowerShell itself depends on Newtonsoft.Json:

PS> [System.AppDomain]::CurrentDomain.GetAssemblies() | where { $_.Location.Contains("Newton") }

GAC    Version        Location
---    -------        --------
False  v4.0.30319     c:\Program Files\PowerShell\6.0.1\Newtonsoft.Json.dll (currently - version 10.0.3)

It means that if someone imports module which depends on newer version of Newtonsoft.Json then some of its commands will fail with error:

Could not load file or assembly 'Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. Could not find or load a specific file. (Exception from HRESULT: 0x80131621)

But it seems to me that this issue is rather general. I mean it must have existed even before PowerShell Core and .NET Core. And there is no solution?

@curiousdev

This comment has been minimized.

Copy link

curiousdev commented Feb 27, 2018

@SteveL-MSFT @xenalite @daxian-dbw when documenting workarounds please consider the scenario that we cannot recompile the 3rd party compiled modules, nor can we manually add binderRedirects to powershell.exe.config.

The only workaround that we've identified is...
Update our large ps1 scripts, spawn portions of the work into child powershell.exe's with a collection of decomposed scripts. This anti-pattern results in the mashing of teeth and rending of garments.

@SteveL-MSFT

This comment has been minimized.

Copy link
Member

SteveL-MSFT commented Feb 27, 2018

@curiousdev workarounds are temporal, plan is to have a proper solution. No ETA at this time as it's not actively being worked on due to other priorities.

@curiousdev

This comment has been minimized.

Copy link

curiousdev commented Feb 27, 2018

@SteveL-MSFT Seeing that it's marked Up-for-Grabs, what's the best way to discuss the solution so a community member can push forward on this? Can you share internal team discussion? If a solution has been identified, I'm happy to consider implementing it for review.

@SteveL-MSFT

This comment has been minimized.

Copy link
Member

SteveL-MSFT commented Feb 27, 2018

@curiousdev the Up-for-Grabs label was removed 26 days ago, but I would greatly appreciate if you would like to take this on! There hasn't been any internal discussion yet on this so you are free to propose a solution. @daxian-dbw would be best to review your proposal. You can just start by having a discussion in this issue. Should the need arise it may make sense to author a RFC.

@astral-keks

This comment has been minimized.

Copy link

astral-keks commented Feb 28, 2018

Even if AppDomains are not supported in .NET core, the issue still remains

@curiousdev And even though AppDomains are not supported it looks like they have something new to solve dependency problem - Assembly Load Contexts:
https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/assemblyloadcontext.md

@bergmeister

This comment has been minimized.

Copy link
Contributor

bergmeister commented Apr 19, 2018

@SteveL-MSFT This is the issue that we briefly talked about at psconf

@iSazonov

This comment has been minimized.

Copy link
Collaborator

iSazonov commented Apr 20, 2018

I think we could solve this after .Net Core 2.1 - we get assembly appdomains or context.

@bergmeister

This comment has been minimized.

Copy link
Contributor

bergmeister commented May 14, 2018

Now that we have netcore2.1 it makes me sad seeing this now only being considered for 6.2, is thjs due to the complexity of the implementation?
It would be great if there was at least documentation on it, especially since the special case by @xenalite here causes also issues on Windows PowerShell, which already has AppDomains integration.
Wouldn't this issue be a great candidate for the feature flags that you want to trial @SteveL-MSFT ?

@SteveL-MSFT

This comment has been minimized.

Copy link
Member

SteveL-MSFT commented May 14, 2018

@bergmeister the challenge for 6.1 is we're getting towards the end and this work wasn't something we initially planned and making the changes now may prove risky (in terms of regression).

@Jaykul

This comment has been minimized.

Copy link

Jaykul commented May 15, 2018

One way we can do things like this is using remoting. If you could remote to your own computer without needing to be elevated, you could do something like this:

$s = New-PSSession -Local
Import-Module Storage -PSSession $s

And we could just make a shortcut by adding a switch to Import-Module to simplify it: -InNewSession

Anything else we could do to "work around" the inability to load different versions of the same assembly is going to have other compromises. That's the nature of the problem.

I mean, one of the strengths of PowerShell is that it's one single app, with all the commands just being methods in classes. That means we can output real objects and pass them around and call methods on them in different commands from different modules.

Every layer of abstraction that you add to that (Load Contexts, AppDomains, and even separate Applications) adds extra work that has to be done and/or removes functionality. If you have separate Applications, you have to serialize your objects, and you loose the ability to call methods, etc. If you have AppDomains you can pass objects, but only if you load the metadata for them on both AppDomains, and it requires remoting calls and object passing, etc.


It seemed, a few posts back, that there have been conversations about this on the side which suggest that this could be done with AppDomains or LoadContexts in a way that would not have huge performance implications -- can anyone explain more? My best understanding of AppDomains would add a lot of caveats: we'd be able to load (and possibly unload) multiple versions of the same assembly ...

But PowerShell would have to not just load each module in it's own app domain, but all of the module's dependencies. At least the metadata for every object being referenced (i.e. all the PowerShell classes) has to be loaded in each AppDomain (and if we want unloading, we have to JIT compile each assembly into each AppDomain where you want to use it)

Then, PowerShell would need to handle inter-domain communication between every command in every pipeline. I can't even imagine the perf impact of doing remoting in between every command in a pipeline.

I think we'd have to avoid loading every module into it's own AppDomain, for performance reasons -- so there would have to be some logic to try and minimize the number of AppDomains while still using "enough" of them to handle as many versions of an assembly as we need to...

How is this different with LoadContexts?

I can't quite tell from the linked document -- it looks to me like there's very little isolation (and thus, no ability to unload), I want to say maybe there's no need for remoting, since it's not mentioned in the linked article, but I can't tell...

How would a script module that loaded a specific version of an assembly avoid using the wrong one? How would a unrelated script which uses commands from two different modules that perhaps use different versions of, say, a logging module ... know which assembly LoadContext to use?

@iSazonov

This comment has been minimized.

Copy link
Collaborator

iSazonov commented May 15, 2018

I think we'd have to avoid loading every module into it's own AppDomain, for performance reasons -- so there would have to be some logic to try and minimize the number of AppDomains while still using "enough" of them to handle as many versions of an assembly as we need to...

I very hope that we can avoid. Perhaps we could Import-Module -Isolate to minimize AppDomains.
As for performance it seems CoreCLR use a type mappings so I think there is some ways to get a solution without performance lost.

How would a unrelated script which uses commands from two different modules that perhaps use different versions of, say, a logging module ... know which assembly LoadContext to use?

I believe we haven't conflicts if cmdlet types is mapped in global context.

fszlin added a commit to fszlin/certes that referenced this issue May 30, 2018

Changed to Newtonsoft.Json 10.0.3 for PowerShell Core (#115)
* Changed to Newtonsoft.Json 10.0.3 for PowerShell Core

PowerShell Core only supports one version of a referenced assembly per session and it already references Newtonsoft.Json 10.0.3 internally, so provides no opportunity for Certes to reference version 11.x. Until this situation is resolved by Microsoft (potentially in PowerShell Core 6.2) the only workaround is to change the dependency here. See PowerShell/PowerShell#2083 and PowerShell/PowerShell#6757

#112

@SteveL-MSFT SteveL-MSFT modified the milestones: 6.2.0-Consider, Future Jun 21, 2018

@Mattacks

This comment has been minimized.

Copy link

Mattacks commented Jul 16, 2018

What's the resolution for when I need to reference a newer version of NewtonSoft than that which ships with PowerShell core 6? (See @astral-keks comment)

@iSazonov

This comment has been minimized.

Copy link
Collaborator

iSazonov commented Jul 17, 2018

@Mattacks I guess you could use application.config to map a dll to new version.

Currently we always use latest version of packages in preview and release PowerShell Core versions.

@Mattacks

This comment has been minimized.

Copy link

Mattacks commented Jul 17, 2018

@iSazonov What do you mean by 'application.config'?
Notwithstanding the fact that developers such as ourselves require a specific version because of dependencies we are bringing in, what would be the process if a different version of NewtonSoft would be needed for, e.g., security bug-fix reasons?

@iSazonov

This comment has been minimized.

Copy link
Collaborator

iSazonov commented Jul 17, 2018

We update continuously packages to get latest security fixes.

Redirecting assemblies https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/redirect-assembly-versions.

@Mattacks

This comment has been minimized.

Copy link

Mattacks commented Jul 17, 2018

Wow, you did mean that feature from the .Net ark! I'll look into it, but that can only be a short term hack, can't it - you can't propose it as a long term solution.

We ship our product to customers. They might stick with a version of powershell installed on their kit for years. They have historically. That's different from upgrading our modules.

I think you have a significant issue here. We need to be able to easily reference the version of newtonsoft we need. And when a dependency ups the version, it needs to flow through seamlessly.

Do I need to raise a separate issue?

@iSazonov

This comment has been minimized.

Copy link
Collaborator

iSazonov commented Jul 17, 2018

You could consider using LoadContext to resolve version conflicts https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/assemblyloadcontext.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment