从Powershell调用AppDomain.DoCallback

时间:2017-08-08 07:33:24

标签: powershell reflection

这是基于Stack Overflow问题:How to load an assembly as reflection-only in a new AppDomain?

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

直接装载组件
[Reflection.Assembly]::ReflectionOnlyLoadFrom($assembly)
因此,

将无效,因为程序集只能在app-domain中加载一次。

给定以下函数在单独的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
    })
}

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

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

,结果都是相同的

任何帮助/指导赞赏

2 个答案:

答案 0 :(得分:1)

首先想到的是:根本不要乱用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无法以这种方式进行远程委托(因为,在封面下,它会生成动态方法)。

但是,托管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的程序集,因此我不认为将此作为优先事项。

答案 1 :(得分:0)

您无法单独通过PowerShell运行DoCallback。但DoCallBack确实可以使用一些内联C#。正如Jeroen所说的那样丑陋,但这很有效:

MiniProfiler.Settings.MaxJsonResponseSize = int.MaxValue;

测试出来:

$assm = "C:\temp\so\bin\dynamic-assembly.dll"

Add-Type -TypeDefinition @"

using System.Reflection;
using System;

namespace Example
{


    public class AppDomainUtil
    {


        public void LoadInAppDomain(AppDomain childDomain, string assemblyName)
        {
            childDomain.SetData("assemblyName", assemblyName);

            childDomain.DoCallBack( new CrossAppDomainDelegate(LoadAssembly)) ;
        }

        public static void LoadAssembly() 
        {

            string assemblyName = (string)AppDomain.CurrentDomain.GetData("assemblyName");

            // console not available from another domain
            string log = "c:\\temp\\hello.txt";

            System.IO.File.WriteAllText(log, string.Format("Hello from {0}\r\n",AppDomain.CurrentDomain.FriendlyName));

            System.IO.File.AppendAllText(log, string.Format("Assembly to load is {0}\r\n",assemblyName));

            Assembly loaded = Assembly.Load(assemblyName);

            System.IO.File.AppendAllText(log, string.Format("Assemblyloaded: {0}\r\n",loaded.FullName));
        }

    }



}
"@ -OutputAssembly $assm -OutputType Library # must set output assembly otherwise assembly generated in-memory and it will break with Type errors.

Add-Type -Path $assm

function Load-AssemblyInNewAppDomain([string]$assembly) {

    Write-Host "Parent domain: $([AppDomain]::CurrentDomain.FriendlyName)"

    $util = New-Object Example.AppDomainUtil

    $ads = New-Object System.AppDomainSetup

    $cd = [AppDomain]::CurrentDomain

    # set application base 
    $ads.ApplicationBase =  [IO.path]::GetDirectoryName( $assm )

    [System.AppDomain]$newDomain = [System.AppDomain]::CreateDomain([System.Guid]::NewGuid().ToString(), $null, $ads);
    Write-Host "Created child domain: $($newDomain.FriendlyName)"

    $util.LoadInAppDomain($newDomain, $assembly)
}