Calling AppDomain.DoCallback from Powershell



这是基于堆栈溢出问题:如何在新的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,结果都是相同的

。感谢任何帮助/指导

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

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

那么,PowerShell中的AppDomain根本没有办法做到这一点吗?嗯,有,但它并不漂亮。您不能使用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的程序集,因此我认为解决此问题不是优先事项。

你不能单独通过powershell运行DoCallback。但是DoCallBack确实适用于一些内联C#。正如杰罗恩所说,这很丑陋,但这有效:

$assm = "C:tempsobindynamic-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}rn",AppDomain.CurrentDomain.FriendlyName));
System.IO.File.AppendAllText(log, string.Format("Assembly to load is {0}rn",assemblyName));
Assembly loaded = Assembly.Load(assemblyName);
System.IO.File.AppendAllText(log, string.Format("Assemblyloaded: {0}rn",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:WINDOWSsystem32> Load-AssemblyInNewAppDomain "".GetType().Assembly.FullName
Parent domain: PowerShell_ISE.exe
Created child domain: 61ab2dbb-8b33-4e7e-84db-5fabfded53aa
PS C:WINDOWSsystem32> cat C:temphello.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

最新更新