这基于堆栈溢出问题:How to load an assembly as reflection-only in a new AppDomain?

我试图确定程序集的运行时版本,但是当我遍历嵌套文件夹时,该程序集可能会多次加载。使用直接加载装配件

[Reflection.Assembly]::ReflectionOnlyLoadFrom($assembly)

因此将无法使用,因为该程序集只能在应用程序域中加载一次。

具有以下功能,可将程序集加载到单独的AppDomain中:
function Load-AssemblyInNewAppDomain($assembly)
{
    Write-Host $assembly.FullName
    $domain = [AppDomain]::CreateDomain([Guid]::NewGuid())
    $domain.DoCallback
    ({
        $loaded = [Reflection.Assembly]::Load($assembly)
        $runtime = $loaded.ImageRuntimeVersion
        Write-Host $runtime
    })
}

这会将委托(delegate)的内容输出到控制台,而不是执行它:
OverloadDefinitions
-------------------
void DoCallBack(System.CrossAppDomainDelegate callBackDelegate)
void _AppDomain.DoCallBack(System.CrossAppDomainDelegate theDelegate)


        $loaded = [Reflection.Assembly]::Load($assembly)

        $runtime = $loaded.ImageRuntimeVersion
        Write-Host $runtime

请注意,无论使用PowerShell 4还是5,结果都是相同的

任何帮助/指导表示赞赏

最佳答案

首先想到的是:不要完全与AppDomains混为一谈,而要使用完全独立的过程。至少(相对)很容易从PowerShell启动它们。缺点是,如果要对许多文件执行此操作,则速度可能会慢得多。

$myAssemblyPath = "C:\..."
$getImageRuntimeVersion = {
    [Reflection.Assembly]::ReflectionOnlyLoadFrom($input).ImageRuntimeVersion
}
$encodedCommand = [Convert]::ToBase64String(
    [Text.Encoding]::Unicode.GetBytes($getImageRuntimeVersion)
)
$imageRuntimeVersion = $myAssemblyPath | powershell -EncodedCommand $encodedCommand

因此,有没有办法在PowerShell中使用AppDomains做到这一点?好吧,有,但是不是很漂亮。您不能使用AppDomain.DoCallBack,因为正如您所发现的那样,PowerShell无法以这种方式远程委托(delegate)(因为在幕后,它会生成动态方法)。

但是,托管PowerShell运行时很容易,并且所有PowerShell对象都知道如何序列化(跨域远程处理的要求),因此在另一个AppDomain中调用PowerShell脚本相当简单(但仍然很丑陋):
$scriptInvokerAssembly = [System.IO.Path]::GetTempFileName() + ".dll"
Add-Type -OutputAssembly $tempAssembly -TypeDefinition @"
  using System;
  using System.Reflection;
  using System.Collections.Generic;
  using System.Management.Automation;

  public class ScriptInvoker : MarshalByRefObject {
    public IEnumerable<PSObject> Invoke(ScriptBlock scriptBlock, PSObject[] parameters) {
      using (var powerShell = PowerShell.Create()) {
        powerShell.Commands.AddScript(scriptBlock.ToString());
        if (parameters != null) {
          powerShell.AddParameters(parameters);
        }
        return powerShell.Invoke();
      }
    }
  }
"@
[Reflection.Assembly]::LoadFile($scriptInvokerAssembly) | Out-Null

Function Invoke-CommandInTemporaryAppDomain([ScriptBlock] $s, [object[]] $arguments) {
  $setup = New-Object System.AppDomainSetup
  $setup.ApplicationBase = Split-Path ([ScriptInvoker].Assembly.Location) -Parent
  $domain = [AppDomain]::CreateDomain([Guid]::NewGuid(), $null, $setup)
  $scriptInvoker = $domain.CreateInstanceAndUnwrap(
     [ScriptInvoker].Assembly.FullName, [ScriptInvoker]
  );
  $scriptInvoker.Invoke($s, $arguments)
  [AppDomain]::Unload($domain)
}

现在你可以做
Invoke-CommandInTemporaryAppDomain {
  [Reflection.Assembly]::ReflectionOnlyLoadFrom($args[0]).ImageRuntimeVersion
} $myAssemblyPath

注意,我们必须在磁盘上生成一个临时程序集,并让AppDomain从那里加载它。这很丑陋,但是您无法让Add-Type生成内存中的程序集,即使您最终以byte[]来获取要在另一个AppDomain中加载的组件,也并非易事,因为您无法在PowerShell中挂钩AppDomain.AssemblyResolve。如果此命令打包在模块中,则需要提前编译包含ScriptInvoker的程序集,因此我认为解决此问题不是优先事项。

关于powershell - 从Powershell调用AppDomain.DoCallback,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45562003/

10-14 11:38
查看更多