-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Workarounds in loading assembly with different versions #11571
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
Comments
/cc @daxian-dbw for information. |
PowerShellEditorServices had some similar issues, @rjmholt recently worked around that with |
There's only one AppDomain in .NET Core, but as it happens this is much easier to make work in .NET Core than in .NET Framework (i.e. if you have a need to do this in .NET Framework you need inter-AppDomain serialisation and a whole lot of
Yeah, if you control the assembly load of your dependency, you can load it into a new assembly load context. In PSES we accomplish this as follows:
This assembly resolve setup means you don't need to use reflection anywhere, you just redirect loading through a new ALC at runtime. But it requires some layers:
We've discussed getting this into PowerShell itself for modules, but it's quite complex and not planned currently, so unlikely to happen within the next 12 months at least. |
I should say that if you're rehosting PowerShell, you can condense the layers I describe above; you'll just have:
|
@rjmholt first and foremost, thanks a ton for your details write-up, it's extremely helpful! Some clarification:
Follow-up questions:
|
I want to point out that it won't work well to load PowerShell into a custom ALC. PowerShell depends on |
That's probably a scoping issue. By the time your resolving handler is invoked, |
Oh sorry! To be clear, hosting PowerShell you should host it in the default ALC, but load the dependency into a custom ALC. Anyway, it sounds like you really just want a module here.
So you don't really need this, but if you don't then the only way to use the dependency APIs you need is by using reflection, which is slower, more error prone and generally less convenient. But you need some unique way to call the API from one assembly and not the other. The two ways to do this are reflection or implicit runtime redirection through a facade API, since just calling the API the normal way in code running in the default ALC will result in you calling the other dependency API.
PowerShell has its own assembly resolve event that fires before yours. When you load the Az module, that loads its immediate assembly. That assembly then needs its dependencies, which aren't found in $PSHOME (.NET's app root here), so PowerShell's resolve event handler gets called, which looks in the directory where that first assembly is loaded. So for example:
When you load When you load So the two never clash. But it depends on the The other way to do it is to load your |
@rjmholt I think I'm getting closer with your extremely useful explanation above 🙇 Using the PSES as an example, if I understood correctly from the code, here is what happens:
If I understood correctly, when I applies the same logic in my code, the overwritten Alternatively, I played with following approach instead:
This seems to be working, and here is what I assumed happening:
|
The problem with the approach above is, (at least what seems to be), if I import |
So the important details are:
For example: s_customLoadContext = new MyLoadContext(s_isolatedBinDir)
AssemblyLoadContext.Default.Resolving += (AssemblyLoadContext defaultLoadContext, AssemblyName asmName) =>
{
Console.WriteLine($"Resolving {asmName.Name}.dll");
if (asmName.Name != s_facadeAsmName
|| asmName.Version != s_facadeAsmVersion)
{
return null;
}
// Possibly s_customLoadContext.LoadFromAssemblyName(asmName) would work here too
// and deduplicate knowledge of the bin dir path
return s_customLoadContext.LoadFromAssemblyPath(Path.Combine(s_isolatedBinDir, $"{s_facadeAsmName}.dll"));
};
internal class MyLoadContext : AssemblyLoadContext
{
private readonly string _dependencyDirPath;
public MyLoadContext(string binDirPath)
{
_dependencyDirPath = binDirPath;
}
protected override Assembly Load(AssemblyName assemblyName)
{
string asmPath = Path.Join(_dependencyDirPath, $"{assemblyName.Name}.dll");
if (File.Exists(asmPath))
{
return LoadFromAssemblyPath(asmPath);
}
return null;
}
} Also, I'm not entirely sure why your custom ALC's load isn't being called, but the way I approached it was to log things everywhere to trace the logic |
One thought here: if an assembly is loaded into a context and you try to load another assembly of the same name into that context, it won't try to load the new one and there won't be any resolve event either. So it may be that you've already loaded the assembly you want into the default ALC, meaning no resolve event, meaning no load call on the custom ALC. |
This issue has been marked as answered and has not had any activity for 1 day. It has been closed for housekeeping purposes. |
Uh oh!
There was an error while loading. Please reload this page.
In our particular case, we have a custom shell around
ConsoleShell
that upon start up loadsMicrosoft.WindowsAzure.Storage.dll
into the current app domain. Once the shell is up, we couldn't import for exampleAz.Accounts
module because it depends on a different version ofMicrosoft.WindowsAzure.Storage.dll
. Since this is a more general problem with PowerShell Core, I posted the issue here instead of over at Azure PowerShell. Effectively, I'm looking for a workaround for issue #2083.Attempt 1:
custom assembly resolve
Problem:
When run two import (
Import-Module C:\version1\assembly.dll; Import-Module C:\version2\assembly.dll
) with different versions of the assembly, I hit error ofImport-Module: Assembly with same name is already loaded
but theAssemblyResolve
event was not raised.Attempt 2:
binding redirect through config file
Problem:
Run into the same error above
Attempt 3:
run any external commands with Invoke-Command -Session, this basically out-sourced the command on a different app domain and avoid the version collision.
Problem:
This seems to be the only workaround I can come up with, but it's really inconvenient to jump back and forth PS sessions.
Question:
Any better workaround?
The text was updated successfully, but these errors were encountered: