Search code examples
.netpowershellassembly-binding-redirect

Different behaviour for same code when in a powershell module vs directly in my script


I was having problems loading one of our DLLs that has references that need Newtonsoft.Json.dll version 8.0.0.0 or 6.0.0.0, but our assembly uses 9.0.1.0. This is not a problem with binding redirects in its app.config when used elsewhere in our solution, but it is when loading our binary in powershell to call one of its functions.

Following davidpodhola's answer, I did exactly what he suggested in order to redirect Version=8.0.0.0 or Version=6.0.0.0 of Newtonsoft.Json.dll to version 9.0.1.0 which is the version we use in all our projects. We need to load that dll a few times in different scripts, so I made a powershell module in order to call a function which looks like this:

function LoadTfsToolsDll(
    [Parameter(Mandatory=$true)]
    [string] $pathToTfsToolsDll
    )
{
    Try
    {
        $parentFolder = Split-Path -Path $pathToTfsToolsDll
        $pathToNewtonSoft = [io.path]::combine($parentFolder, 'Newtonsoft.Json.dll')
        $newtonsoft = [reflection.assembly]::LoadFrom($pathToNewtonSoft) 

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

          # from:NewtonsoftJson, Version=8.0.0.0 or Version=6.0.0.0
          # to:  NewtonsoftJson, Version=9.0.1.0
          if ($e.Name -eq "Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed") { return $newtonsoft }
          if ($e.Name -eq "Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed") { return $newtonsoft }

          foreach($a in [System.AppDomain]::CurrentDomain.GetAssemblies())
          {
            if ($a.FullName -eq $e.Name)
            {
              return $a
            }
          }
          return $null
        }

        [System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve)
    }
    Catch
    {
        throw [System.Exception]::new("Problem doing Newtonsoft.Json's binding redirect. See Inner exception.", $PSItem.Exception)
    }

    Try
    {
        Add-Type -Path $pathToTfsToolsDll
    }
    Catch
    {
        throw [System.Exception]::new("Problem loading TfsTools.dll. See Inner exception.", $PSItem.Exception)
    }
}

Export-ModuleMember -Function LoadTfsToolsDll

Unfortunately, importing the above module using Import-Module, and directly copying the above code at the top of my script (so it is ran first before using our assembly's function) does not result in the same behaviour.

When having it written directly in my script, it is ran without exceptions, and I can then call my assembly's function without any problem. However, when importing the module at the beginning of my script, no exception is thrown, but then when calling my assembly's function I get the following exception, which was supposed to be fixed by my module's code:

Exception calling "getSpecificBuildVNext" with "4" argument(s): "Could not load file or assembly 'Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The 
system cannot find the file specified."
At C:\Script\Test Run Scripts\Vision Tests\GCS Tests\Steps\InstallSC.ps1:70 char:4
+             $build = [TFS]::getSpecificBuildVNext('https://tfs.genete ...
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : FileNotFoundException

What is the difference between both of those solutions, and how can I make it work with a powershell module instead of copying the same code several times in different scripts directly?


Solution

  • From my own research, it seems that:

    1. A new PS session is created when a module is imported for that module
    2. When running one of the module's function, it is ran from the module's PS session, and not the script's PS session

    This would mean that the binary I was trying to load (and do a binding redirect on) is only loaded and redirected properly in the module's PS session, and not my calling script's session.