Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolving assembly dependency references with powershell

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?

like image 501
DaveShaw Avatar asked Apr 24 '14 12:04

DaveShaw


People also ask

How do I Unimport a PowerShell module?

-ModuleInfoSpecifies the module objects to remove. Enter a variable that contains a module object (PSModuleInfo) or a command that gets a module object, such as a Get-Module command. You can also pipe module objects to Remove-Module .

What is PowerShell reflection Assembly?

Reflection. Assembly class. This means that you can pass it to the Get-Member Windows PowerShell cmdlet and see what methods, properties and events are available.

How do I remove a PowerShell module?

To uninstall the PowerShell module, we can directly use the Uninstall-Module command but the module should not be in use, otherwise, it will throw an error. When we use the Uninstall-Module command, it can uninstall the module from the current user profile or from the all users profile.


1 Answers

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)"
}
like image 94
CJBS Avatar answered Sep 18 '22 06:09

CJBS