使用 powershell 解析程序集依赖项引用 [英] Resolving assembly dependency references with powershell

查看:34
本文介绍了使用 powershell 解析程序集依赖项引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试针对我们的内部 API 之一使用 PowerShell v4.0 (x86/64) 来做一些相当基本的事情,但我似乎无法通过依赖加载.

到目前为止我有:

[Reflection.Assembly]::LoadFrom("C:UsersDavid ShawDesktopAPIAPI.dll")

根据 Dat Bui 的 博文.

这很好用,然后我尝试在这个 DLL 中使用一个类型:

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

这给了我以下错误:

New-Object:使用2"个参数调用.ctor"的异常:无法找到程序集 API.Dependency,版本=1.2.5.0,文化=中性,PublicKeyToken=null'."在行:1 字符:6+ $a = 新对象 API.API("", 1234)+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException+ FullQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

在FusionLog中查找,唯一查找依赖的地方是:C:WindowsSystem32WindowsPowerShellv1.0

到目前为止我尝试过的事情:

  • 设置 powershell 当前目录.
  • 将其作为脚本而不是从控制台编写.
  • 我在与 API.dll 相同的位置有依赖项
  • 使用 LoadFile 而不是 LoadFrom
  • 使用Add-Type -Path API.dll
  • 设置 .net CurrentDirectory
  • 对依赖项调用 LoadFrom.
  • 在 Powershell 中执行 AppDomain.AssemblyResolve 事件见下文,但此堆栈溢出 powershell:

来自我的脚本:

$OnAssemblyResolve = [System.ResolveEventHandler] {参数($sender, $e)$n = 新对象 System.Reflection.AssemblyName($e.Name).Name$fn = "C:UsersDavid ShawDesktopAPI$n.dll"返回 [Reflection.Assembly]::LoadFile($fn)}[System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve)

根据评论,API.dll 是 .Net 4.0 (AnyCPU),API.Dependency.dll 是 .Net 2.0 (AnyCPU).如果这可能是一个问题,知道如何解决吗?

解决方案

我有一个与 NuGet 包类似的问题,我有两个程序集都使用了一个 NuGet 包(特别是 Spring.NET 库).我的第一个 C# 项目 (My1stProject) 使用了 .NET Framework v4.0,因此包含了特定于该框架版本的 NuGet 包 DLL.第二个 C# 项目 (My2ndProject) 以 .NET Framework v4.7 为目标,因此从 NuGet 包中获得了 v4.5 程序集.项目 My2ndProject 依赖于 My1stProject.

当我编译代码时,一切正常.项目 My2ndProject 已编译,但 NuGet 包中包含的程序集适用于 v4.5 框架.

现在,当我尝试 - 在 My2ndProject 的二进制输出目录中 - 使用 Powershell 代码加载和获取程序集类型时:$assembly = [System.Reflection.Assembly]::LoadFrom($My1stProjectFullPath),然后是 $assembly.GetTypes(),这将由于版本差异而失败——v4.5 NuGet DLL 在那里,但它期待 v4.0.>

所以,按照这个优秀的代码示例,我的解决方案是预加载我需要忽略版本的二进制文件(以便将它们加载到应用程序域中),然后使用一些与程序集解析过程挂钩的代码(类似于 OP 中的代码)问题)和:

  • 首先尝试根据全名(包括版本、区域设置等)进行加载匹配.
  • 如果失败,则尝试仅对名称进行匹配(忽略版本等)

代码如下:

$candidateAssembly = "C:My2ndProjectinDebugMy1stProject.exe"# 加载程序集的目标版本(这些来自 NuGet 包,并且# 版本与 My2ndProject.exe 期望的不兼容)[System.Reflection.Assembly]::LoadFrom("C:My2ndProjectinDebugSpring.Aop.dll")[System.Reflection.Assembly]::LoadFrom("C:My2ndProjectinDebugSpring.Core.dll")[System.Reflection.Assembly]::LoadFrom("C:My2ndProjectinDebugSpring.Data.dll")# 拦截二进制文件解析的方法$onAssemblyResolveEventHandler = [System.ResolveEventHandler] {参数($sender, $e)写主机ResolveEventHandler:尝试 $($e.Name) 的全名解析"foreach($assembly in [System.AppDomain]::CurrentDomain.GetAssemblies()) {如果($assembly.FullName -eq $e.Name){写主机成功的全名解析 $($e.Name)"返回 $assembly}}写主机ResolveEventHandler:尝试 $($e.Name) 的仅名称解析"foreach($assembly in [System.AppDomain]::CurrentDomain.GetAssemblies()) {# 仅从 FullName 中获取名称(无版本)$assemblyName = $assembly.FullName.Substring(0, $assembly.FullName.IndexOf(", "))if ($e.Name.StartsWith($($assemblyName + ","))) {写入主机$assemblyName 的仅名称(无版本)解析成功"返回 $assembly}}写主机无法解析 $($e.Name)"返回 $null}# 连线事件处理程序[System.AppDomain]::CurrentDomain.add_AssemblyResolve($onAssemblyResolveEventHandler)# 加载到应用程序域$assembly = [System.Reflection.Assembly]::LoadFrom($candidateAssembly)尝试{# 这确保所有依赖项都被正确加载$assembly.GetTypes()}捕获 [System.Reflection.ReflectionTypeLoadException]{写主机消息:$($_.Exception.Message)"写主机StackTrace:$($_.Exception.StackTrace)"写主机LoaderExceptions:$($_.Exception.LoaderExceptions)"}

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:UsersDavid ShawDesktopAPIAPI.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:WindowsSystem32WindowsPowerShellv1.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:UsersDavid ShawDesktopAPI$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?

解决方案

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:My2ndProjectinDebugMy1stProject.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:My2ndProjectinDebugSpring.Aop.dll")
[System.Reflection.Assembly]::LoadFrom("C:My2ndProjectinDebugSpring.Core.dll")
[System.Reflection.Assembly]::LoadFrom("C:My2ndProjectinDebugSpring.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)"
}

这篇关于使用 powershell 解析程序集依赖项引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆