Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PowerShell Binary Module assembly dependency error

I am developing PowerShell binary module. It uses Json.NET and other libraries.

I am getting this exception "Could not load file or assembly 'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified.'

On hard drive I have an updated version of it (version 7.0.2)

Problems like that are easily solved in console, web or desktop application, with app.config or "web.config" via lines like this

<dependentAssembly>
    <assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
    <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
  </dependentAssembly>

How can I do something similar for PowerShell binary module?

like image 852
TIKSN Avatar asked Sep 04 '15 12:09

TIKSN


2 Answers

After coming across this issue myself while developing a PowerShell module that uses multiple 3rd party libraries (Google API, Dropbox, Graph, etc) I found the following solution was the simplest:

public static Assembly CurrentDomain_BindingRedirect(object sender, ResolveEventArgs args)
{
    var name = new AssemblyName(args.Name);
    switch (name.Name)
    {
        case "Microsoft.Graph.Core":
            return typeof(Microsoft.Graph.IBaseClient).Assembly;

        case "Newtonsoft.Json":
            return typeof(Newtonsoft.Json.JsonSerializer).Assembly;

        case "System.Net.Http.Primitives":
            return Assembly.LoadFrom("System.Net.Http.Primitives.dll");

        default:
            return null;
    }
}

Note in the method, I've got two possible ways to reference the assembly, but both of them do the same thing, they force the current version of that assembly to be used. (Regardless if it is loaded via class reference or dll file load)

To use this in any cmd-let add the following event handler in the BeginProcessing() method of the PSCmdLet.

AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_BindingRedirect;
like image 78
Martin Laukkanen Avatar answered Sep 21 '22 16:09

Martin Laukkanen


The closest i've found so far is:

  1. Add the problem assembly to the manifest's RequiredAssemblies - this causes it to be loaded into the AppDomain when the module is loaded.
  2. Use the code from this SO answer - it adds an AssemblyResolve handler to the current AppDomain, which searches for assemblies already loaded and returns ones which match by strong name and PublicKeyToken
  3. After using the module, you have to do the following to avoid stack overflows when exiting: [System.AppDomain]::CurrentDomain.remove_AssemblyResolve($OnAssemblyResolve)

Steps 1 and 2 could both be encapsulated in the module, but step 3 can't, which means this isn't suitable as a general solution - the caller has to know about it. So I'm still searching for a better way.

like image 27
ben Avatar answered Sep 18 '22 16:09

ben