从 Powershell 调用 AppDomain.DoCallback [英] Calling AppDomain.DoCallback from Powershell

查看:55
本文介绍了从 Powershell 调用 AppDomain.DoCallback的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是基于堆栈溢出问题:如何在新的 AppDomain 中将程序集作为仅反射加载?

This is based on the Stack Overflow question: How to load an assembly as reflection-only in a new AppDomain?

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

I am attempting to determine the runtime version of an assembly, but that assembly could be loaded multiple times as I traverse through nested folders. Loading the assembly directly using

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

因此将不起作用,因为程序集只能在应用程序域中加载一次.

will therefore not work, as the assembly can only be loaded once in the app-domain.

给定以下函数以在单独的 AppDomain 中加载程序集:

Given the following function to load an assembly in a separate 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
    })
}

这会将委托的内容输出到控制台,而不是执行它:

This outputs the contents of the delegate to the console, rather than executing it:

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,结果都是一样的

Note that the results are the same, whether I use PowerShell 4 or 5

感谢任何帮助/指导

推荐答案

第一个想法:完全不要使用 AppDomains 并使用完全独立的流程.至少,这些(相对)很容易从 PowerShell 启动.缺点是,如果您对大量文件执行此操作,速度可能会慢得多.

First thought: don't muck around with AppDomains at all and use a completely separate process. Those are (relatively) easily launched from PowerShell, at least. The drawback is that it's potentially much slower if you're doing this for lots of files.

$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 不能以这种方式远程委托(因为在幕后,它会生成动态方法).

So, is there no way at all to do this with AppDomains in PowerShell? Well, there is, but it's not pretty. You can't use AppDomain.DoCallBack because, as you've discovered, PowerShell can't remote delegates that way (because, under the covers, it produces dynamic methods).

但是,托管 PowerShell 运行时很容易,并且所有 PowerShell 对象都知道如何序列化(跨域远程处理的要求),因此在另一个 AppDomain 中调用 PowerShell 脚本相当简单(但仍然很难看):

However, it's easy to host the PowerShell runtime, and all PowerShell objects know how to serialize (a requirement for cross-domain remoting), so invoking a PowerShell script in another AppDomain is fairly simple (but still ugly):

$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 的程序集,所以我不认为解决这个问题是优先事项.

Note that we have to generate a temporary assembly on disk and have AppDomain load it from there. This is ugly, but you can't have Add-Type produce an in-memory assembly, and even if you do end up with a byte[] getting that to load in another AppDomain is anything but trivial because you can't hook AppDomain.AssemblyResolve in PowerShell. If this command was packaged in a module, you'd compile the assembly containing the ScriptInvoker ahead of time, so I don't see working around this as a priority.

这篇关于从 Powershell 调用 AppDomain.DoCallback的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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