从 Powershell 调用 AppDomain.DoCallback [英] Calling AppDomain.DoCallback from Powershell
问题描述
这是基于堆栈溢出问题:如何在新的 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屋!