Powershell config assembly redirect
Asked Answered
S

3

21

I have a custom .NET assembly with some powershell cmdlets than I use for common domain related tasks. I've just created a new cmdlet that references a 3rd party library that has a reference to Newtonsoft.Json 4.5.0.0. However one of my other projects uses the latest version of json.net (6.0.0.0). So at runtime in powershell fusion throws an error saying it can't load newtonsoft.json 4.5.0.0.

I've tried creating a powershell.exe.config and putting an assembly redirect in there:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json", Culture=neutral,     PublicKeyToken=30ad4fe6b2a6aeed/>
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

but this doesn't seem to work. The fusion log does state that it is looking in this new config file for powershell but it doesn't seem to be picking up the redirect.

Bit stumped for solutions here. Any clues what the problem might be? This same redirect works in some of my business services that would otherwise have the same issue (they also use this 3rd party lib and json.net 6).

Cheers

Stove answered 12/6, 2014 at 9:58 Comment(0)
S
38

Not sure how it worked more than year ago, however today on Windows 10 using PowerShell 5.0.10240.16384 the only way I was able to do assembly redirect (in my case from FSharp.Core 4.3 to 4.4) was to manually resolve assembly dependencies based on Manually resolving assembly dependencies in PowerShell. I tried every other solutions like creating the powershell.exe.config file or trying to load some other *.config file, but nothing of those worked.

The only "gotcha" (at lease for me) was, that since I do not have FSharp.Core 4.3 anywhere, I needed to manually redirect it to 4.4. I ended up using

$FSharpCore = [reflection.assembly]::LoadFrom($PSScriptRoot + "\bin\LIBRARY\FSharp.Core.dll") 

$OnAssemblyResolve = [System.ResolveEventHandler] {
  param($sender, $e)

  # from:FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
  # to:  FSharp.Core, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
  if ($e.Name -eq "FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") { return $FSharpCore }
  
  foreach($a in [System.AppDomain]::CurrentDomain.GetAssemblies())
  {
    if ($a.FullName -eq $e.Name)
    {
      return $a
    }
  }
  return $null
}

[System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve)

where I am first loading the correct version of FSharp.Core from somewhere as the version in the GAC is old (I guess this might be your case too)

You can also check the real test usage in my project.

Station answered 23/10, 2015 at 12:44 Comment(2)
Already September 2017 and I confirm that this is the only solution that works, also applies to Windows Server 2016. You've saved my day, thanks!Week
This works perfect for me. Note that it works in PowerShell (including the PowerShell window in Visual Studio code), but not in PowerShell ISE (or the PowerShell Integrated Console in Visual Studio Code)--the latter may experience stack overflow or crash of unknown reason.Peace
L
3

Based on @davidpodhola answer, I needed something generic where I wanted it to resolve to an assembly with the same name as long as it's loaded, and I'd take the responsibility of loading the correct version ahead of time.

This way, this code can be generic and you can add it to any script that does a wild-card resolution on only the assembly name. Added here in case anyone else needs that:

$OnAssemblyResolve = [System.ResolveEventHandler] {

    param($sender, $e)

    $searchFor = $null
    if ($e.Name -match "(.*?), .*") {
        $searchFor = $matches[1]
    }
    foreach ($a in [System.AppDomain]::CurrentDomain.GetAssemblies()) {

        $foundItem = $null
        if ($a.FullName -match "(.*?), .*") {
            $foundItem = $matches[1]
        }
    
        if ($foundItem -eq $searchFor) {
            return $a
        }
    }
    return $null
}
[System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve)
Lalalalage answered 19/10, 2022 at 1:42 Comment(0)
A
1

I have found that trying to resolve assemblies with a handler attached in PowerShell using [System.AppDomain]::CurrentDomain.add_AssemblyResolve can cause issues, particularly in PowerShell ISE.

Alternatively, this could be handled in the root module assembly (containing the PSCmdlet classes) by implementing the IModuleAssemblyInitializer interface.

Using the accepted answer as an example, it could be rewritten as:

public class ModuleInitializer : IModuleAssemblyInitializer
{
    public void OnImport()
    {
        var fSharpCore = Assembly.LoadFrom(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "FSharp.Core.dll"));

        AppDomain.CurrentDomain.AssemblyResolve += (object sender, ResolveEventArgs e) =>
        {
            // from:FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
            // to:  FSharp.Core, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
            if (e.Name == "FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") { return fSharpCore; }

            foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (a.FullName == e.Name)
                {
                    return a;
                }
            }

            return null;
        };
    }
}

In my case, I used this mechanism to look for an acommpanying .dll.config and redirect according to the dependentAssembly/bindingRedirect elements.

Ardeb answered 1/6, 2021 at 22:23 Comment(1)
If writing modules for PowerShell Core, you may have some better options, see: devblogs.microsoft.com/powershell/…Ardeb

© 2022 - 2025 — McMap. All rights reserved.