I need to dynamically resolve assembly references from one class library to another. The class libraries are being loaded from a PowerShell script, so the default .NET behaviour of looking for dependent assemblies in the executable directly fails, as the executable is PowerShell itself. How do I make these dependent assembly references resolve / work correctly?
In more detail:
I have two utility libraries: a core one and another one that does some very specific parsing tasks. I want to load them dynamically in a PowerShell script without installing them in the GAC. The second library depends on the first. In the VS solution, the parsing library has a project reference to the core library, with
Copy Local =
I can load and use both libraries from the parsing library output bin (/Debug|/Release) folder after using (PowerShell here):
However, whenever calling a method in the parsing (dependent) library that calls something from the core library it fails to resolve the core assembly. This is...frustrating...since the files are in the same folder. It makes no difference whether one or both have strong name keys.
My workaround now is to handle the
AssemblyResolve event. The tricky thing is figuring out where to put this in a class library, since there's no single entry point that will always execute before anything else like there is in an executable
Main() method (see Is there an equivalent of Application_Start for a class library in c#).
For now I've made a static
Resolver class with a static constructor that attaches a handler for
AssemblyResolve, and then have a static constructor in each of the parsing classes which refers to the static resolver class, forcing the resolver class's static constructor to execute. The result is that the AssemblyResolve event gets attached exactly once and handled with common, central code. So it works. But I hate having to add a funky static constructor to all of my parsing classes.
Is there a better way to handle this?
I figured out a solution that follows the "consumer should resolve" pattern, and which works for both PowerShell and normal .NET application consumers.
Resolverclass from the same or another class library, invoke it directly by the consumer. When PowerShell is the consumer, invoke the
CurrentDomainas the assemblies it loads. So even if the event handler is attached in some dynamically loaded assembly, it will still be invoked when a assembly resolve fails in the main consuming application.
My version of
AssemblyDirectorythat can be used to optionally set the directory to search from. If left blank, it will use the directory found from
Register()method which really does nothing except ensure the static constructor has been called.
# First, load the assembly with the Resolver class. I actually put it in the same # core library, but it really doesn't matter where it is. It could even be by itself # in a dynamically compiled assembly built using the Add-Type commandlet [Reflection.Assembly]::LoadFile("[an assembly that has the Resolver class].dll") # Call the Register method to force the static constructor to run if it hasn't already [mycorp.mylib.Resolver]::Register() $sourcedir = "C:\foo\bar\..some random dir" # Set the path to search for unresolved assemblies [mycorp.mylib.Resolver]::AssemblyDirectory = $sourcedir # Load the dependent assembly [Reflection.Assembly]::LoadFile("$sourcedir\myparser.dll") # Create and use an object from the dependent assembly. When the core assembly # fails at first to resolve, the Resolver's handler is automatically called, # and everything is peachy $encoder = New-Object mycorp.mylib.anamespace.aclass $result = $encoder.DoStuff( $x, $y, $z ... etc )
If you want to know how to actually handle the AssemblyResolve event, check out the documentation on MSDN: AppDomain.AssemblyResolve Event
At first I tried to build an assembly resolver directly in PowerShell, and register it using the
Register-ObjectEvent commandlet. This would work, but for one problem: PowerShell doesn't support event handlers that return a value. AssemblyResolve handlers return an Assembly object. That's almost their whole purpose.