Calling AppDomain.DoCallback from Powershell

后端 未结 2 1438
暗喜
暗喜 2021-01-20 02:10

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

相关标签:
2条回答
  • 2021-01-20 02:49

    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.

    0 讨论(0)
  • 2021-01-20 03:02

    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
    
    0 讨论(0)
提交回复
热议问题