Calling AppDomain.DoCallback from Powershell

This is based on the question: How do I load an assembly as a reflection in a new AppDomain only?

I am trying to determine the version of the runtime, but this assembly may be loaded multiple times when I traverse subfolders. Loading the assembly directly with

[Reflection.Assembly]::ReflectionOnlyLoadFrom($assembly)

      

therefore won't work as the assembly can only be loaded once in the application domain.

Given the following function to load the 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 prints 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

      

Please note that the results are the same whether I am using PowerShell 4 or 5

Any help / guidance is appreciated

+3


source to share


2 answers


First thought: Don't fool AppDomains at all and don't use an entirely separate process. They are (relatively) easy to run from PowerShell, at least. The downside is that it is potentially much slower if you are doing this for a lot of files.

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

      

So, isn't there a general way to do this with AppDomains in PowerShell? Well, there is, but it's ugly. You can't use AppDomain.DoCallBack

because, as you discovered, PowerShell can't remove delegates this way (because under the covers, it creates 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 calling a PowerShell script on a different AppDomain is pretty 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

      

Please note that we need to create a temporary assembly on disk and AppDomain

load it there. It's ugly, but you can't create the Add-Type

assembly in memory, and even if you end up getting byte[]

to load it in a different AppDomain, it's nothing but trivial because you can't intercept AppDomain.AssemblyResolve

in PowerShell. If this command was packaged into a module, you have prebuilt the assembly containing it ScriptInvoker

, so I do not consider it a priority.

0


source


You cannot run DoCallback through powershell. But DoCallBack works with some built-in C #. As Jerouen says, it's ugly, but it 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:

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

      

0


source







All Articles