Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to cast transparent proxy in a dll when called from PowerShell, but successful in C# console app

I'm attempting to create an open source library that spawn a new AppDomain and runs a PowerShell script in it. I have a static method that takes the name of the powershell file and the name of the AppDomain. The method executes successfully when called from a C# console app, but not PowerShell.

I know the dll is being loaded in the second app domain because of this entry in the fusionlog.

The class declaraton and constructor looks like this.

public class AppDomainPoshRunner : MarshalByRefObject{

    public AppDomainPoshRunner (){
        Console.WriteLine("Made it here.");
    }
}

That message in the constructor gets output when I call CreateInstanceFromAndUnwrap whether I run the dll from a C# console app or from the PowerShell app.

The failure occurs when I cast the value returned by CreateInstanceFromAndUnwrap to AppDomainPoshRunner in the static method below.

    public static string[] RunScriptInAppDomain(string fileName, string appDomainName = "Unamed")
    {
        var assembly = Assembly.GetExecutingAssembly();

        var setupInfo = new AppDomainSetup
                            {
                                ApplicationName = appDomainName,
                                // TODO: Perhaps we should setup an even handler to reload the AppDomain similar to ASP.NET in IIS.
                                ShadowCopyFiles = "true"
                            };
        var appDomain = AppDomain.CreateDomain(string.Format("AppDomainPoshRunner-{0}", appDomainName), null, setupInfo);
        try {
            var runner = appDomain.CreateInstanceFromAndUnwrap(assembly.Location, typeof(AppDomainPoshRunner).FullName);
            if (RemotingServices.IsTransparentProxy(runner))
                Console.WriteLine("The unwrapped object is a proxy.");
            else
                Console.WriteLine("The unwrapped object is not a proxy!");  
            Console.WriteLine("The unwrapped project is a {0}", runner.GetType().FullName);
            /* This is where the error happens */
            return ((AppDomainPoshRunner)runner).RunScript(fileName);
        }
        finally
        {
            AppDomain.Unload(appDomain);
        }
    }

When running that in PowerShell I get an InvalidCastExcception with the message Unable to cast transparent proxy to type JustAProgrammer.ADPR.AppDomainPoshRunner.

What am I doing wrong?

like image 762
Justin Dearing Avatar asked Sep 06 '25 10:09

Justin Dearing


2 Answers

I had the same problem: I created sandbox with Execute only permissions (the min one can have) to execute untrusted code in very restricted environment. All worked great in C# application, but did not work (the same cast exception) when starting point was vbs script creating .NET COM object. I think PowerShell also uses COM. I found workaround using AppDomain.DoCallBack, which avoids getting proxy from the appdomain. This is the code. If you find a better option, please post. Registering in GAC is not a good solution for me...

    class Test
    {
        /*
         create appdomain as usually
        */

        public static object Execute(AppDomain appDomain, Type type, string method, params object[] parameters)
        {
            var call = new CallObject(type, method, parameters);
            appDomain.DoCallBack(call.Execute);
            return call.GetResult();
        }
    }
    [Serializable]
    public class CallObject
    {
        internal CallObject(Type type, string method, object[] parameters)
        {
            this.type = type;
            this.method = method;
            this.parameters = parameters;
        }

        [PermissionSet(SecurityAction.Assert, Unrestricted = true)]
        public void Execute()
        {
            object instance = Activator.CreateInstance(this.type);
            MethodInfo target = this.type.GetMethod(this.method);
            this.result.Data = target.Invoke(instance, this.parameters);
        }

        internal object GetResult()
        {
            return result.Data;
        }

        private readonly string method;
        private readonly object[] parameters;
        private readonly Type type;
        private readonly CallResult result = new CallResult();

        private class CallResult : MarshalByRefObject
        {
            internal object Data { get; set; }
        }
    }
like image 72
Dima Avatar answered Sep 09 '25 01:09

Dima


It sounds very much like a loading context issue. Type identity isn't just about the physical assembly file; it's also about how and where it was loaded. Here's an old blog post from Suzanne Cook that you'll probably have to read fifteen times until you can begin to make sense of your problem.

Choosing a Binding Context

https://learn.microsoft.com/en-us/archive/blogs/suzcook/choosing-a-binding-context

Before you say "but it works in a console app," remember that when running it from powershell, you have an entirely different kettle of fish as regards the calling appdomain's context, probing paths, identity etc.

Good luck!

like image 37
x0n Avatar answered Sep 09 '25 01:09

x0n