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 a
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
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).
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)
}
And now you can do
Invoke-CommandInTemporaryAppDomain {
[Reflection.Assembly]::ReflectionOnlyLoadFrom($args[0]).ImageRuntimeVersion
} $myAssemblyPath
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.
You can't run DoCallback via powershell alone. But DoCallBack does work with some inline C#. As Jeroen says it's ugly, but this works:
$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)
}
Testing it out:
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