13

I am trying to use PowerShell v4.0 (x86/64) against one of our internal API's to do some fairly basic stuff, but I cannot seem to get past the dependency loading.

So far I have:

[Reflection.Assembly]::LoadFrom("C:\Users\David Shaw\Desktop\API\API.dll")

as per Dat Bui's blog post.

This works fine, I then try to use a type inside this DLL:

$a = New-Object API.API("", 1234)

This gives me the following error:

New-Object : Exception calling ".ctor" with "2" argument(s): "Unable to find assembly API.Dependency, 
Version=1.2.5.0, Culture=neutral, PublicKeyToken=null'."
At line:1 char:6
+ $a = New-Object API.API("", 1234)
+      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [New-Object], MethodInvocationException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

Looking in FusionLog, the only places it looks for the dependency is: C:\Windows\System32\WindowsPowerShell\v1.0

Things I've tried so far:

  • Setting the powershell current dir.
  • Writing it as a script instead of from the console.
  • I have the dependency in the same location as API.dll
  • Using LoadFile instead of LoadFrom
  • Using Add-Type -Path API.dll
  • Setting the .net CurrentDirectory
  • Calling LoadFrom on the dependency.
  • Doing an AppDomain.AssemblyResolve event in Powershell see below, but this stack overflows powershell:

From my Script:

$OnAssemblyResolve = [System.ResolveEventHandler] {
  param($sender, $e)
    $n = New-Object System.Reflection.AssemblyName($e.Name).Name
      $fn = "C:\Users\David Shaw\Desktop\API\$n.dll"
      return [Reflection.Assembly]::LoadFile($fn)       
}

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

Based on the comment, the API.dll is .Net 4.0 (AnyCPU) and the API.Dependency.dll is .Net 2.0 (AnyCPU). If this could be an issue, any idea's how to resolve it?

7
  • 1
    Wild guesses here ... Might it be something related to the supported runtime? With what version of .Net are your API.dll and its dependencies compiled? Or something related to the bitness of your DLL not corresponding to the bitness of the PowerShell you use? Commented Apr 24, 2014 at 13:31
  • @DavidBrabant I've tried both powershell versions, no difference. I've added the .net versions to the question at the end. Commented Apr 24, 2014 at 16:14
  • I'm surprised that Add-Type -Path C:\Users\David Shaw\Desktop\API\API.dll; Add-Type -Path C:\Users\David Shaw\Desktop\API\API.Dependency.dll did not work. Or at the very least point to yet another dependent assembly the loader can't find. Commented Apr 24, 2014 at 16:15
  • @KeithHill, just to be sure, I have used Add-Type -Path on every DLL that is needed for this API. And it still fails the same way. Commented Apr 24, 2014 at 16:23
  • 1
    As a quick experiment to determine if this is a loader issue or something else, copy all the required assemblies into C:\Windows\System32\WindowsPowerShell\v1.0. If that still fails, then there is something else going on (missing an assembly dependency, etc). Commented Apr 25, 2014 at 15:42

1 Answer 1

10

I had a similar problem with NuGet packages whereby I had two assemblies that both used a NuGet package (specifically, the Spring.NET libraries). My first C# project (My1stProject) used the .NET Framework v4.0, and thus included the NuGet package DLL specific to that Framework version. A second C# project (My2ndProject) targeted the .NET Framework v4.7, and thus got the v4.5 assembly from the NuGet package. Project My2ndProject had a dependency on My1stProject.

When I compiled the code, everything worked. Project My2ndProject compiled, but the included assembly from the NuGet package was for the v4.5 Framework.

Now, when I attempted - in My2ndProject's binary output directory - to use Powershell code to load and get types for the assembly: $assembly = [System.Reflection.Assembly]::LoadFrom($My1stProjectFullPath), followed by $assembly.GetTypes(), this would fail due to the version discrepancy -- the v4.5 NuGet DLL was there, but it was expecting the v4.0 one.

So, following this excellent code example, my solution is to pre-load the binaries that I need to ignore the version for (so that they are loaded into the App Domain), then use some code that hooks into the assembly resolution process (similar to that in the OP question) and:

  • first attempt to do a load match based on the full name (including version, locale, etc.
  • and if that failed, attempt to do the match on name only (ignoring version etc.)

Here is the code:

$candidateAssembly =  "C:\My2ndProject\bin\Debug\My1stProject.exe"

# Load your target version of the assembly (these were from the NuGet package, and 
# have a version incompatible with what My2ndProject.exe expects)
[System.Reflection.Assembly]::LoadFrom("C:\My2ndProject\bin\Debug\Spring.Aop.dll")
[System.Reflection.Assembly]::LoadFrom("C:\My2ndProject\bin\Debug\Spring.Core.dll")
[System.Reflection.Assembly]::LoadFrom("C:\My2ndProject\bin\Debug\Spring.Data.dll")

# Method to intercept resolution of binaries
$onAssemblyResolveEventHandler = [System.ResolveEventHandler] {
    param($sender, $e)

    Write-Host "ResolveEventHandler: Attempting FullName resolution of $($e.Name)" 
    foreach($assembly in [System.AppDomain]::CurrentDomain.GetAssemblies()) {
        if ($assembly.FullName -eq $e.Name) {
            Write-Host "Successful FullName resolution of $($e.Name)" 
            return $assembly
        }
    }

    Write-Host "ResolveEventHandler: Attempting name-only resolution of $($e.Name)" 
    foreach($assembly in [System.AppDomain]::CurrentDomain.GetAssemblies()) {
        # Get just the name from the FullName (no version)
        $assemblyName = $assembly.FullName.Substring(0, $assembly.FullName.IndexOf(", "))

        if ($e.Name.StartsWith($($assemblyName + ","))) {

            Write-Host "Successful name-only (no version) resolution of $assemblyName" 
            return $assembly
        }
    }

    Write-Host "Unable to resolve $($e.Name)" 
    return $null
}

# Wire-up event handler
[System.AppDomain]::CurrentDomain.add_AssemblyResolve($onAssemblyResolveEventHandler)

# Load into app domain
$assembly = [System.Reflection.Assembly]::LoadFrom($candidateAssembly) 

try
{
    # this ensures that all dependencies were loaded correctly
    $assembly.GetTypes() 
} 
catch [System.Reflection.ReflectionTypeLoadException] 
{ 
     Write-Host "Message: $($_.Exception.Message)" 
     Write-Host "StackTrace: $($_.Exception.StackTrace)"
     Write-Host "LoaderExceptions: $($_.Exception.LoaderExceptions)"
}
Sign up to request clarification or add additional context in comments.

3 Comments

This got me past my first binding issue on System.Threading.Tasks.Extensions.dll, but the next one for System.Runtime.CompilerServices.Unsafe.dll failed to work. Couldn't figure out why, it seems GetTypes() wouldn't trigger the resolve handler for that dll.
powershell load custom1.dll, then load custom2.dll referencing custom1.dll in result load error, this code saved me. This is beyond my understanding that DLL already in the AppDomain is not automatically used when the library referencing this DLL is loaded... PS. i forgot about this event, but i used it like 8 years ago :)
this seems to crash with a page guard exception in ISE

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.