【问题标题】:Calling AppDomain.DoCallback from Powershell从 Powershell 调用 AppDomain.DoCallback
【发布时间】:2017-08-10 08:10:12
【问题描述】:

这是基于堆栈溢出问题: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
    })
}

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

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

感谢任何帮助/指导

【问题讨论】:

  • 去掉 DoCallback 和左括号之间的换行符
  • 删除换行符现在允许块执行,但失败:异常调用“DoCallBack”与“1”参数:“无法序列化非托管函数指针、动态方法或外部方法的委托委托创建者的程序集。”

标签: powershell reflection


【解决方案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 中也绝非易事,因为你不能挂钩 @ 987654328@ 在 PowerShell 中。如果这个命令被打包在一个模块中,你会提前编译包含ScriptInvoker 的程序集,所以我不认为优先解决这个问题。

【讨论】:

    【解决方案2】:

    您不能仅通过 powershell 运行 DoCallback。但是 DoCallBack 确实适用于一些内联 C#。正如 Jeroen 所说,这很丑,但这很有效:

    $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)
    }
    

    测试一下:

    PS C:\WINDOWS\system32> Load-AssemblyInNewAppDomain "".GetType().Assembly.FullName
    
    Parent domain: PowerShell_ISE.exe
    Created child domain: 61ab2dbb-8b33-4e7e-84db-5fabfded53aa
    
    PS C:\WINDOWS\system32> cat C:\temp\hello.txt
    
    Hello from 61ab2dbb-8b33-4e7e-84db-5fabfded53aa
    Assembly to load is mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    Assemblyloaded: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-15
      • 2020-01-02
      • 2012-10-26
      • 2010-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多