I am trying to integrate IronPython into a C# application for scripting purposes. I would also like the scripts to run under a set of security policies that restrict their access to the file system/network/sensitive system resources.
Research indicates that the most popular approach is to use CAS. This works very well and it does not allow the user to use sockets, access the file system, etc. However, when I try to inject variables for the scrip to interact with, I get a security exception every time I access certain parameters. This only happens with variables I define in my assembly. If I use a standard .NET type (like Dictionary) it works just fine.
My Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Security.Policy;
using System.Security.Permissions;
using System.Security;
using System.IO;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
using System.Runtime.Remoting;
using System.Net;
namespace python_in_appdomain
{
[Serializable]
public class Whatever
{
public int i;
public Whatever(int i)
{
this.i = i;
}
}
class Program
{
static void Main(string[] args)
{
Remoting();
}
public static void Remoting()
{
AppDomainSetup setup = new AppDomainSetup();
Assembly thisAssembly = Assembly.GetExecutingAssembly();
setup.ApplicationBase = Path.GetDirectoryName(thisAssembly.Location);
AssemblyName name = typeof(Program).Assembly.GetName();
StrongName sn = new StrongName(
new StrongNamePublicKeyBlob(name.GetPublicKey()),
name.Name,
name.Version
);
PermissionSet pset = new PermissionSet(PermissionState.None);
pset.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
pset.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));
setup.PartialTrustVisibleAssemblies = new[] { sn.Name + ", PublicKey=" + sn.PublicKey.ToString() };
AppDomain domain = AppDomain.CreateDomain("Sandbox", AppDomain.CurrentDomain.Evidence, setup, pset, sn);
String script = #"
d.Add('hello', 1)
w.i = 5
";
Whatever w = new Whatever(4);
Dictionary<string, int> d = new Dictionary<string,int>();
ScriptRuntime py = Python.CreateRuntime(domain);
ScriptEngine engine = py.GetEngine("py");
ScriptScope scope = engine.CreateScope();
scope.SetVariable("w", w);
scope.SetVariable("d", d);
int result;
Console.WriteLine(w.i);
d.TryGetValue("hello", out result);
Console.WriteLine(result);
Console.WriteLine("-----");
try
{
engine.Execute(script, scope);
}
catch (Exception exc)
{
Console.WriteLine(exc.ToString());
}
w = scope.GetVariable<Whatever>("w");
d = scope.GetVariable<Dictionary<string,int>>("d");
Console.WriteLine("-----");
Console.WriteLine(w.i);
d.TryGetValue("hello", out result);
Console.WriteLine(result);
}
}
}
Commenting out the "w.i=5" on line 55 causes the program to execute normally under restricted security settings. Setting the PermissionState to Unrestricted on line 47 allows both lines of the script to execute normally. The following is the error message I receive:
System.Security.SecurityException: Request failed.
at System.Runtime.CompilerServices.RuntimeHelpers._CompileMethod(IRuntimeMethodInfo method)
at System.Reflection.Emit.DynamicMethod.CreateDelegate(Type delegateType, Object target)
at System.Dynamic.Utils.TypeExtensions.CreateDelegate(MethodInfo methodInfo, Type delegateType, Object target)
at System.Linq.Expressions.Compiler.LambdaCompiler.CreateDelegate()
at System.Linq.Expressions.Compiler.LambdaCompiler.Compile(LambdaExpression lambda, DebugInfoGenerator debugInfoGenerator)
at System.Linq.Expressions.Expression`1.Compile()
at System.Runtime.CompilerServices.CallSiteBinder.BindCore[T](CallSite`1 site, Object[] args)
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at Microsoft.Scripting.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
at Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1)
at IronPython.Compiler.PythonScriptCode.RunWorker(CodeContext ctx)
at IronPython.Compiler.PythonScriptCode.Run(Scope scope)
at IronPython.Compiler.RuntimeScriptCode.InvokeTarget(Scope scope)
at IronPython.Compiler.RuntimeScriptCode.Run(Scope scope)
at Microsoft.Scripting.SourceUnit.Execute(Scope scope, ErrorSink errorSink)
at Microsoft.Scripting.Hosting.ScriptSource.Execute(ScriptScope scope)
at Microsoft.Scripting.Hosting.ScriptEngine.Execute(String expression, ScriptScope scope)
at Microsoft.Scripting.Hosting.ScriptEngine.Execute(String expression, ScriptScope scope)
at python_in_appdomain.Program.Remoting() in C:\Users\dave\Documents\Visual Studio 2010\Projects\IronPythonTest\IronPythonTest\Program.cs:line 7
6
The action that failed was:
Demand
The type of the first permission that failed was:
System.Security.PermissionSet
The demand was for:
<PermissionSet class="System.Security.PermissionSet"
version="1"
Unrestricted="true"/>
The granted set of the failing assembly was:
<PermissionSet class="System.Security.PermissionSet"
version="1">
<IPermission class="System.Security.Permissions.ReflectionPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
version="1"
Unrestricted="true"/>
<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
version="1"
Flags="Execution"/>
</PermissionSet>
The assembly or AppDomain that failed was:
Microsoft.Dynamic, Version=1.1.0.20, Culture=neutral, PublicKeyToken=7f709c5b713576e1
The method that caused the failure was:
Int32 Run(Microsoft.Scripting.Interpreter.InterpretedFrame)
The Zone of the assembly that failed was:
Internet
The Url of the assembly that failed was:
file:///C:/Users/dave/Documents/Visual Studio 2010/Projects/IronPythonTest/IronPythonTest/bin/Debug/Microsoft.Dynamic.DLL
Unhandled Exception: System.Security.SecurityException: Request failed.
at System.Delegate.BindToMethodInfo(Object target, IRuntimeMethodInfo method, RuntimeType methodType, DelegateBindingFlags flags)
at System.Delegate.CreateDelegate(Type type, MethodInfo method, Boolean throwOnBindFailure)
at System.Dynamic.Utils.TypeExtensions.CreateDelegate(MethodInfo methodInfo, Type delegateType)
at System.Runtime.CompilerServices.CallSite`1.MakeUpdateDelegate()
at System.Runtime.CompilerServices.CallSite`1.GetUpdateDelegate(T& addr)
at System.Runtime.CompilerServices.CallSite`1.GetUpdateDelegate()
at System.Runtime.CompilerServices.CallSite`1.Create(CallSiteBinder binder)
at System.Func`2.Invoke(T arg)
at Microsoft.Scripting.Runtime.DynamicOperations.GetOrCreateSite[T](CallSiteBinder siteBinder, Func`2 factory)
at Microsoft.Scripting.Runtime.DynamicOperations.GetOrCreateSite[T1,TResult](CallSiteBinder siteBinder)
at Microsoft.Scripting.Runtime.DynamicOperations.ConvertTo[T](Object obj)
at IronPython.Runtime.PythonContext.ScopeGetVariable[T](Scope scope, String name)
at Microsoft.Scripting.Hosting.ScriptScope.GetVariable[T](String name)
at Microsoft.Scripting.Hosting.ScriptScope.GetVariable[T](String name)
at python_in_appdomain.Program.Remoting() in C:\Users\dave\Documents\Visual Studio 2010\Projects\IronPythonTest\IronPythonTest\Program.cs:line 8
3
at python_in_appdomain.Program.Main(String[] args) in C:\Users\dave\Documents\Visual Studio 2010\Projects\IronPythonTest\IronPythonTest\Program.
cs:line 31
The error is shockingly straight forward. It says that it is expecting to have totally unrestricted access for this kind of action. Is there any way around this? Why does the dictionary work but my variable does not? Is there a way for me to make IronPython treat my variable like the Dictionary? Am I missing something very obvious?
Thank you very much for you help.
Update 12/21/2012
I kept messing around and came up with a solution that worked. Creating the AppDomain like this (with the same permission object) allows everything to work.
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = System.Environment.CurrentDirectory;
AppDomain domain = AppDomain.CreateDomain("IPyEngine", new Evidence(), setup, pset);
I can't say I totally understand why, though I can see that I'm not providing my assembly's Evidence nor am I adding any Assemblies to the trust list. I don't want to mark this as the answer until I know for certain I'm not doing anything monumentally stupid.
This is suspicious:
The Zone of the assembly that failed was:
Internet
The Url of the assembly that failed was:
file:///C:/Users/dave/Documents/Visual Studio 2010/Projects/IronPythonTest/IronPythonTest/bin/Debug/Microsoft.Dynamic.DLL
I wonder if the file has a zone identifier. Can you check the file properties, and Unblock it if necessary?
Related
I have built an assembly that make calls to the ClosedXML library. Everything works perfectly until I have to export large Excel files,
then things go south and I receive the following exception:
System.IO.IsolatedStorage.IsolatedStorageException: Unable to determine the identity of domain.
at System.IO.IsolatedStorage.IsolatedStorage._GetAccountingInfo(Evidence evidence, Type evidenceType, IsolatedStorageScope fAssmDomApp, Object& oNormalized)
at System.IO.IsolatedStorage.IsolatedStorage.GetAccountingInfo(Evidence evidence, Type evidenceType, IsolatedStorageScope fAssmDomApp, String& typeName, String& instanceName)
at System.IO.IsolatedStorage.IsolatedStorage._InitStore(IsolatedStorageScope scope, Evidence domainEv, Type domainEvidenceType, Evidence assemEv, Type assemblyEvidenceType, Evidence appEv, Type appEvidenceType)
at System.IO.IsolatedStorage.IsolatedStorage.InitStore(IsolatedStorageScope scope, Type domainEvidenceType, Type assemblyEvidenceType)
at System.IO.IsolatedStorage.IsolatedStorageFile.GetStore(IsolatedStorageScope scope, Type domainEvidenceType, Type assemblyEvidenceType)
at MS.Internal.IO.Packaging.PackagingUtilities.ReliableIsolatedStorageFileFolder.GetCurrentStore()...
So, after some research, I found some info on how to address the above exception, which is detailed in the questions below:
EPPlus 2.9.0.1 throws System.IO.IsolatedStorage.IsolatedStorageException when trying to save a file bigger than ~1.5 MiB from a SSIS package
Unable to determine the identity of domain using System.IO.Packaging
Isolated Storage Exception: Unable to determine the identity of domain
As suggested in the answers to the questions above, I made sure that all of the classes in my custom assembly inherit from MarshalByRefObject and are marked as Serializable.
I have also added a custom assembly ResolveEventHandler.
Saying that, because I am loading the assembly from a file at runtime (this is done because I do not have the option of adding it to the GAC), I am having trouble figuring out how to correctly implement the new domain and its resolve event. When I execute the code below, I recieve following error:
repository.GetWorkFlows() is called:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: ''System.MarshalByRefObject' does not contain a definition for 'GetWorkFlows''
Code:
using System;
using System.Data.SqlClient;
using System.Reflection;
namespace ST_1736b3ab1e134250a6c05bffb44d449f
{
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
public Assembly CustomResolveEventHandler(object sender, ResolveEventArgs args)
{
Assembly assembly = null;
try
{
assembly = LoadAssembly(args, assembly);
}
catch (Exception exception)
{
Dts.Log(exception.ToString(), 0, new byte[0]);
throw;
}
Dts.Log("Exiting CurrentDomain_AssemblyResolve", 0, new byte[0]);
return assembly;
}
public Assembly LoadAssembly(ResolveEventArgs args, Assembly assembly)
{
Dts.Log(string.Format("Entering CurrentDomain_AssemblyResolve with args.Name [{0}]", args.Name), 0, new byte[0]);
string referencedAssembliesFilePath = Dts.Variables["$Package::referencedAssembliesFilePath"].Value.ToString();
var assemblyName = string.Format("{0}.dll", args.Name.Split(',')[0]);
Dts.Log("Assembly Name: " + assemblyName, 0, new byte[0]);
var assemblyFilePath = System.IO.Path.Combine(referencedAssembliesFilePath, assemblyName);
if (System.IO.File.Exists(assemblyFilePath))
{
Dts.Log(string.Format("Currently Loading [{0}]", assemblyFilePath), 0, new byte[0]);
assembly = Assembly.LoadFile(assemblyFilePath);
}
else
{
Dts.Log(string.Format("Could Not Find Assembly File [{0}].", assemblyFilePath), 0, new byte[0]);
}
return assembly;
}
public void Main()
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory.ToString();
Evidence evidence = new Evidence();
evidence.AddHostEvidence(new Zone(System.Security.SecurityZone.MyComputer));
AppDomain domain = AppDomain.CreateDomain("SeparateDomain", evidence, setup);
separateDomain.AssemblyResolve += new ResolveEventHandler(CustomResolveEventHandler);
const string referencedAssemblyName = "SSISDataExportManager";
string query = Dts.Variables["$Package::queryGetDataExportWorkFlowTasks"].Value.ToString();
dynamic repository = separateDomain.CreateInstanceAndUnwrap(referencedAssemblyName, referencedAssemblyName + "." + "DataExportManagerRepository");
using (SqlConnection connection = (SqlConnection)Dts.Connections["DbConnection"].AcquireConnection(Dts.Transaction))
{
if (connection.State == System.Data.ConnectionState.Closed) connection.Open();
//***Exception thrown here***
dynamic workflows = repository.GetWorkFlows(connection, query, 60);
dynamic factory = separateDomain.CreateInstanceAndUnwrap(referencedAssemblyName, "DataExportWorkflowSessionFactory");
dynamic session = factory.CreateSession(workflows);
session.Execute(connection);
}
Dts.TaskResult = (int)ScriptResults.Success;
}
#region ScriptResults declaration
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
}
}
I assume that this is because I need to explicitly cast to the types in my custom assembly, but again, I cannot do this due to it begin dynamically loaded.
What am I missing? Is there another way to do this, or is this even possible?
I want to load to a new AppDomain some assembly which has a complex references tree (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll)
As far as I understood, when an assembly is being loaded to AppDomain, its references would not be loaded automatically, and I have to load them manually.
So when I do:
string dir = #"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory
string path = System.IO.Path.Combine(dir, "MyDll.dll");
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = dir;
AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup);
domain.Load(AssemblyName.GetAssemblyName(path));
and got FileNotFoundException:
Could not load file or assembly 'MyDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
I think the key part is one of its dependencies.
Ok, I do next before domain.Load(AssemblyName.GetAssemblyName(path));
foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
{
domain.Load(refAsmName);
}
But got FileNotFoundException again, on another (referenced) assembly.
How to load all references recursively?
Do I have to create references tree before loading root assembly? How to get an assembly's references without loading it?
You need to invoke CreateInstanceAndUnwrap before your proxy object will execute in the foreign application domain.
class Program
{
static void Main(string[] args)
{
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);
Type type = typeof(Proxy);
var value = (Proxy)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);
var assembly = value.GetAssembly(args[0]);
// AppDomain.Unload(domain);
}
}
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch (Exception)
{
return null;
// throw new InvalidOperationException(ex);
}
}
}
Also, note that if you use LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.
Once you pass the assembly instance back to the caller domain, the caller domain will try to load it! This is why you get the exception. This happens in your last line of code:
domain.Load(AssemblyName.GetAssemblyName(path));
Thus, whatever you want to do with the assembly, should be done in a proxy class - a class which inherit MarshalByRefObject.
Take in count that the caller domain and the new created domain should both have access to the proxy class assembly. If your issue is not too complicated, consider leaving the ApplicationBase folder unchanged, so it will be same as the caller domain folder (the new domain will only load Assemblies it needs).
In simple code:
public void DoStuffInOtherDomain()
{
const string assemblyPath = #"[AsmPath]";
var newDomain = AppDomain.CreateDomain("newDomain");
var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName);
asmLoaderProxy.GetAssembly(assemblyPath);
}
class ProxyDomain : MarshalByRefObject
{
public void GetAssembly(string AssemblyPath)
{
try
{
Assembly.LoadFrom(AssemblyPath);
//If you want to do anything further to that assembly, you need to do it here.
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message, ex);
}
}
}
If you do need to load the assemblies from a folder which is different than you current app domain folder, create the new app domain with specific dlls search path folder.
For example, the app domain creation line from the above code should be replaced with:
var dllsSearchPath = #"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);
This way, all the dlls will automaically be resolved from dllsSearchPath.
http://support.microsoft.com/kb/837908/en-us
C# version:
Create a moderator class and inherit it from MarshalByRefObject:
class ProxyDomain : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFrom(assemblyPath);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
}
call from client site
ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);
On your new AppDomain, try setting an AssemblyResolve event handler. That event gets called when a dependency is missing.
You need to handle the AppDomain.AssemblyResolve or AppDomain.ReflectionOnlyAssemblyResolve events (depending on which load you're doing) in case the referenced assembly is not in the GAC or on the CLR's probing path.
AppDomain.AssemblyResolve
AppDomain.ReflectionOnlyAssemblyResolve
It took me a while to understand #user1996230's answer so I decided to provide a more explicit example. In the below example I make a proxy for an object loaded in another AppDomain and call a method on that object from another domain.
class ProxyObject : MarshalByRefObject
{
private Type _type;
private Object _object;
public void InstantiateObject(string AssemblyPath, string typeName, object[] args)
{
assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory
_type = assembly.GetType(typeName);
_object = Activator.CreateInstance(_type, args); ;
}
public void InvokeMethod(string methodName, object[] args)
{
var methodinfo = _type.GetMethod(methodName);
methodinfo.Invoke(_object, args);
}
}
static void Main(string[] args)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = #"SomePathWithDLLs";
AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup);
ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject");
proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs});
proxyObject.InvokeMethod("foo",new object[] { "bar"});
}
The Key is the AssemblyResolve event raised by the AppDomain.
[STAThread]
static void Main(string[] args)
{
fileDialog.ShowDialog();
string fileName = fileDialog.FileName;
if (string.IsNullOrEmpty(fileName) == false)
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
if (Directory.Exists(#"c:\Provisioning\") == false)
Directory.CreateDirectory(#"c:\Provisioning\");
assemblyDirectory = Path.GetDirectoryName(fileName);
Assembly loadedAssembly = Assembly.LoadFile(fileName);
List<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>();
foreach (var type in assemblyTypes)
{
if (type.IsInterface == false)
{
StreamWriter jsonFile = File.CreateText(string.Format(#"c:\Provisioning\{0}.json", type.Name));
JavaScriptSerializer serializer = new JavaScriptSerializer();
jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type)));
jsonFile.Close();
}
}
}
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string[] tokens = args.Name.Split(",".ToCharArray());
System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name);
return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"}));
}
I have had to do this several times and have researched many different solutions.
The solution I find in most elegant and easy to accomplish can be implemented as such.
1. Create a project that you can create a simple interface
the interface will contain signatures of any members you wish to call.
public interface IExampleProxy
{
string HelloWorld( string name );
}
Its important to keep this project clean and lite. It is a project that both AppDomain's can reference and will allow us to not reference the Assembly we wish to load in seprate domain from our client assembly.
2. Now create project that has the code you want to load in seperate AppDomain.
This project as with the client proj will reference the proxy proj and you will implement the interface.
public interface Example : MarshalByRefObject, IExampleProxy
{
public string HelloWorld( string name )
{
return $"Hello '{ name }'";
}
}
3. Next, in the client project, load code in another AppDomain.
So, now we create a new AppDomain. Can specify the base location for assembly references. Probing will check for dependent assemblies in GAC and in current directory and the AppDomain base loc.
// set up domain and create
AppDomainSetup domaininfo = new AppDomainSetup
{
ApplicationBase = System.Environment.CurrentDirectory
};
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo);
// assembly ant data names
var assemblyName = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
var exampleTypeName = "Example";
// Optional - get a reflection only assembly type reference
var #type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName );
// create a instance of the `Example` and assign to proxy type variable
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName );
// Optional - if you got a type ref
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( #type.Assembly.Name, #type.Name );
// call any members you wish
var stringFromOtherAd = proxy.HelloWorld( "Tommy" );
// unload the `AppDomain`
AppDomain.Unload( exampleDomain );
if you need to, there are a ton of different ways to load an assembly. You can use a different way with this solution. If you have the assembly qualified name then I like to use the CreateInstanceAndUnwrap since it loads the assembly bytes and then instantiates your type for you and returns an object that you can simple cast to your proxy type or if you not that into strongly-typed code you could use the dynamic language runtime and assign the returned object to a dynamic typed variable then just call members on that directly.
There you have it.
This allows to load an assembly that your client proj doesnt have reference to in a seperate AppDomain and call members on it from client.
To test, I like to use the Modules window in Visual Studio. It will show you your client assembly domain and what all modules are loaded in that domain as well your new app domain and what assemblies or modules are loaded in that domain.
The key is to either make sure you code either derives MarshalByRefObject or is serializable.
`MarshalByRefObject will allow you to configure the lifetime of the domain its in. Example, say you want the domain to destroy if the proxy hasnt been called in 20 minutes.
I hope this helps.
I'd like to intercept the communication between a plug-in and a host assembly.
I've tried to load ThirdPartyPlugin.dll into an AppDomain sandbox (code below), to intercept its attempts at loading of HostLibrary.dll assembly (I'm using trick with handling the AssemblyResolve event). Instead of the original HostLibrary.dll, I'm then trying to inject a fake one, with some different functionalities.
Unfortunately, the original HostLibrary.dll is a Strong Named Assembly, and I think because of that I'm getting an exception like below in my program:
Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'HostLibrary, Version=7.0.0.0, Culture=neutral, PublicKeyToken=1a2b3c4d5e6f7890' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) ---> System.IO.FileLoadException: The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
The fake assembly I created does match the name ("HostLibrary"), and the version ("7.0.0.0") of the original assembly; at one time I think I even made it apparently match the PublicKeyToken (when I printed Assembly.FullName, it was identical), but the exception still occurs.
Do you have any ideas as to how I could resolve this problem? Is it possible to resolve at all? Does it happen because of digital signature, or something else? If signature, then is it possible to disable the check? Actually, from reading an article by Ian Picknell, I'd think I should not really be having this problem... so what's wrong?
The code is:
static void Main(string[] args)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = "WrapperBuddy";
setup.ApplicationBase = System.Environment.CurrentDirectory;
AppDomain sandbox = AppDomain.CreateDomain("SandboxBuddy", null, setup);
sandbox.AssemblyResolve += delegate(object sender, ResolveEventArgs args)
{
String name = new AssemblyName(args.Name).Name;
return BuildFakeAssembly(name);
};
sandbox.DoCallBack(delegate()
{
Assembly plugin = LoadAssembly(PLUGIN_DIR, "ThirdPartyPlugin");
System.Console.WriteLine(plugin.GetExportedTypes()); // (1)
});
}
with helper functions defined as:
static Assembly LoadAssembly(String dir, String name)
{
string path = Path.Combine(dir, name) + ".dll";
return Assembly.Load(File.ReadAllBytes(path));
}
static Assembly BuildFakeAssembly(String name)
{
AssemblyName aName = new AssemblyName(name);
// see: http://msdn.microsoft.com/en-us/library/w58ww7se%28v=vs.85%29.aspx
// http://msdn.microsoft.com/en-us/library/w58ww7se%28v=vs.100%29.aspx
aName.KeyPair = new StrongNameKeyPair(new FileStream(#"MyKey.snk", FileMode.Open));
// MyKey.snk is a random key generated using sn.exe
aName.Version = new Version("7.0.0.0");
//aName.SetPublicKeyToken(new byte[] { 0x11, 0x22, ... }); // didn't help
AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
aName, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");
return ab;
}
I'd be grateful for any help.
I asked a while ago how to restrict plugins access ( I want to prevent them from writing to the disk or network ) and i was told to use AppDomain. I have searched and tried and failed on how to get this working.
Can anyone provide some information so i can get started, simply put make a AppDomain that does not allows writing to the file or network.
For .net framework 4.0, please follow the following code from this MSDN article.
The following example implements the procedure in the previous section. In the example, a project named Sandboxer in a Visual Studio solution also contains a project named UntrustedCode, which implements the class UntrustedClass. This scenario assumes that you have downloaded a library assembly containing a method that is expected to return true or false to indicate whether the number you provided is a Fibonacci number. Instead, the method attempts to read a file from your computer. The following example shows the untrusted code.
using System;
using System.IO;
namespace UntrustedCode
{
public class UntrustedClass
{
// Pretend to be a method checking if a number is a Fibonacci
// but which actually attempts to read a file.
public static bool IsFibonacci(int number)
{
File.ReadAllText("C:\\Temp\\file.txt");
return false;
}
}
}
The following example shows the Sandboxer application code that executes the untrusted code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;
using System.Reflection;
using System.Runtime.Remoting;
//The Sandboxer class needs to derive from MarshalByRefObject so that we can create it in another
// AppDomain and refer to it from the default AppDomain.
class Sandboxer : MarshalByRefObject
{
const string pathToUntrusted = #"..\..\..\UntrustedCode\bin\Debug";
const string untrustedAssembly = "UntrustedCode";
const string untrustedClass = "UntrustedCode.UntrustedClass";
const string entryPoint = "IsFibonacci";
private static Object[] parameters = { 45 };
static void Main()
{
//Setting the AppDomainSetup. It is very important to set the ApplicationBase to a folder
//other than the one in which the sandboxer resides.
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);
//Setting the permissions for the AppDomain. We give the permission to execute and to
//read/discover the location where the untrusted code is loaded.
PermissionSet permSet = new PermissionSet(PermissionState.None);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
//We want the sandboxer assembly's strong name, so that we can add it to the full trust list.
StrongName fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<StrongName>();
//Now we have everything we need to create the AppDomain, so let's create it.
AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
//Use CreateInstanceFrom to load an instance of the Sandboxer class into the
//new AppDomain.
ObjectHandle handle = Activator.CreateInstanceFrom(
newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
typeof(Sandboxer).FullName
);
//Unwrap the new domain instance into a reference in this domain and use it to execute the
//untrusted code.
Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
newDomainInstance.ExecuteUntrustedCode(untrustedAssembly, untrustedClass, entryPoint, parameters);
}
public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)
{
//Load the MethodInfo for a method in the new Assembly. This might be a method you know, or
//you can use Assembly.EntryPoint to get to the main function in an executable.
MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
try
{
//Now invoke the method.
bool retVal = (bool)target.Invoke(null, parameters);
}
catch (Exception ex)
{
// When we print informations from a SecurityException extra information can be printed if we are
//calling it with a full-trust stack.
(new PermissionSet(PermissionState.Unrestricted)).Assert();
Console.WriteLine("SecurityException caught:\n{0}", ex.ToString());
CodeAccessPermission.RevertAssert();
Console.ReadLine();
}
}
}
I guess this is what you need, if I understand correctly your point.
System.Security.PermissionSet ps =
new System.Security.PermissionSet(System.Security.Permissions.PermissionState.None);
ps.AddPermission(new System.Security.Permissions.FileIOPermission(System.Security.Permissions.FileIOPermissionAccess.NoAccess, "C:\\"));
System.Security.Policy.PolicyLevel pl = System.Security.Policy.PolicyLevel.CreateAppDomainLevel();
pl.RootCodeGroup.PolicyStatement = new System.Security.Policy.PolicyStatement(ps);
AppDomain.CurrentDomain.SetAppDomainPolicy(pl);
System.Reflection.Assembly myPluginAssembly = AppDomain.CurrentDomain.Load("MyPluginAssembly");
Is this more precisely what you meant?
Notice that you may provide an array of string containg the paths where you don't want the plugin to have access. You may provide if when initializing the new instance of FileIOPermission class.
Let me know if this helps. :-)
If you're using plugins, you might perhaps know about proxies.
While loading your assembly through a proxy, you can specify the security policy level for this particular assembly through the LoadAssembly() method or so, if I remember correctly. In other words, this is done through reflection.
I know my answer isn't that much detailed, but I hope it will give you an idea of where to look for your solution. I shall take an eye out to find further details on the subject so that I may be of better help. =)
Hope you will share your findings when you've done it.
I want to load to a new AppDomain some assembly which has a complex references tree (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll)
As far as I understood, when an assembly is being loaded to AppDomain, its references would not be loaded automatically, and I have to load them manually.
So when I do:
string dir = #"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory
string path = System.IO.Path.Combine(dir, "MyDll.dll");
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = dir;
AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup);
domain.Load(AssemblyName.GetAssemblyName(path));
and got FileNotFoundException:
Could not load file or assembly 'MyDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
I think the key part is one of its dependencies.
Ok, I do next before domain.Load(AssemblyName.GetAssemblyName(path));
foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
{
domain.Load(refAsmName);
}
But got FileNotFoundException again, on another (referenced) assembly.
How to load all references recursively?
Do I have to create references tree before loading root assembly? How to get an assembly's references without loading it?
You need to invoke CreateInstanceAndUnwrap before your proxy object will execute in the foreign application domain.
class Program
{
static void Main(string[] args)
{
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);
Type type = typeof(Proxy);
var value = (Proxy)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);
var assembly = value.GetAssembly(args[0]);
// AppDomain.Unload(domain);
}
}
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch (Exception)
{
return null;
// throw new InvalidOperationException(ex);
}
}
}
Also, note that if you use LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.
Once you pass the assembly instance back to the caller domain, the caller domain will try to load it! This is why you get the exception. This happens in your last line of code:
domain.Load(AssemblyName.GetAssemblyName(path));
Thus, whatever you want to do with the assembly, should be done in a proxy class - a class which inherit MarshalByRefObject.
Take in count that the caller domain and the new created domain should both have access to the proxy class assembly. If your issue is not too complicated, consider leaving the ApplicationBase folder unchanged, so it will be same as the caller domain folder (the new domain will only load Assemblies it needs).
In simple code:
public void DoStuffInOtherDomain()
{
const string assemblyPath = #"[AsmPath]";
var newDomain = AppDomain.CreateDomain("newDomain");
var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName);
asmLoaderProxy.GetAssembly(assemblyPath);
}
class ProxyDomain : MarshalByRefObject
{
public void GetAssembly(string AssemblyPath)
{
try
{
Assembly.LoadFrom(AssemblyPath);
//If you want to do anything further to that assembly, you need to do it here.
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message, ex);
}
}
}
If you do need to load the assemblies from a folder which is different than you current app domain folder, create the new app domain with specific dlls search path folder.
For example, the app domain creation line from the above code should be replaced with:
var dllsSearchPath = #"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);
This way, all the dlls will automaically be resolved from dllsSearchPath.
http://support.microsoft.com/kb/837908/en-us
C# version:
Create a moderator class and inherit it from MarshalByRefObject:
class ProxyDomain : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFrom(assemblyPath);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
}
call from client site
ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);
On your new AppDomain, try setting an AssemblyResolve event handler. That event gets called when a dependency is missing.
You need to handle the AppDomain.AssemblyResolve or AppDomain.ReflectionOnlyAssemblyResolve events (depending on which load you're doing) in case the referenced assembly is not in the GAC or on the CLR's probing path.
AppDomain.AssemblyResolve
AppDomain.ReflectionOnlyAssemblyResolve
It took me a while to understand #user1996230's answer so I decided to provide a more explicit example. In the below example I make a proxy for an object loaded in another AppDomain and call a method on that object from another domain.
class ProxyObject : MarshalByRefObject
{
private Type _type;
private Object _object;
public void InstantiateObject(string AssemblyPath, string typeName, object[] args)
{
assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory
_type = assembly.GetType(typeName);
_object = Activator.CreateInstance(_type, args); ;
}
public void InvokeMethod(string methodName, object[] args)
{
var methodinfo = _type.GetMethod(methodName);
methodinfo.Invoke(_object, args);
}
}
static void Main(string[] args)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = #"SomePathWithDLLs";
AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup);
ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject");
proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs});
proxyObject.InvokeMethod("foo",new object[] { "bar"});
}
The Key is the AssemblyResolve event raised by the AppDomain.
[STAThread]
static void Main(string[] args)
{
fileDialog.ShowDialog();
string fileName = fileDialog.FileName;
if (string.IsNullOrEmpty(fileName) == false)
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
if (Directory.Exists(#"c:\Provisioning\") == false)
Directory.CreateDirectory(#"c:\Provisioning\");
assemblyDirectory = Path.GetDirectoryName(fileName);
Assembly loadedAssembly = Assembly.LoadFile(fileName);
List<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>();
foreach (var type in assemblyTypes)
{
if (type.IsInterface == false)
{
StreamWriter jsonFile = File.CreateText(string.Format(#"c:\Provisioning\{0}.json", type.Name));
JavaScriptSerializer serializer = new JavaScriptSerializer();
jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type)));
jsonFile.Close();
}
}
}
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string[] tokens = args.Name.Split(",".ToCharArray());
System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name);
return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"}));
}
I have had to do this several times and have researched many different solutions.
The solution I find in most elegant and easy to accomplish can be implemented as such.
1. Create a project that you can create a simple interface
the interface will contain signatures of any members you wish to call.
public interface IExampleProxy
{
string HelloWorld( string name );
}
Its important to keep this project clean and lite. It is a project that both AppDomain's can reference and will allow us to not reference the Assembly we wish to load in seprate domain from our client assembly.
2. Now create project that has the code you want to load in seperate AppDomain.
This project as with the client proj will reference the proxy proj and you will implement the interface.
public interface Example : MarshalByRefObject, IExampleProxy
{
public string HelloWorld( string name )
{
return $"Hello '{ name }'";
}
}
3. Next, in the client project, load code in another AppDomain.
So, now we create a new AppDomain. Can specify the base location for assembly references. Probing will check for dependent assemblies in GAC and in current directory and the AppDomain base loc.
// set up domain and create
AppDomainSetup domaininfo = new AppDomainSetup
{
ApplicationBase = System.Environment.CurrentDirectory
};
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo);
// assembly ant data names
var assemblyName = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
var exampleTypeName = "Example";
// Optional - get a reflection only assembly type reference
var #type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName );
// create a instance of the `Example` and assign to proxy type variable
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName );
// Optional - if you got a type ref
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( #type.Assembly.Name, #type.Name );
// call any members you wish
var stringFromOtherAd = proxy.HelloWorld( "Tommy" );
// unload the `AppDomain`
AppDomain.Unload( exampleDomain );
if you need to, there are a ton of different ways to load an assembly. You can use a different way with this solution. If you have the assembly qualified name then I like to use the CreateInstanceAndUnwrap since it loads the assembly bytes and then instantiates your type for you and returns an object that you can simple cast to your proxy type or if you not that into strongly-typed code you could use the dynamic language runtime and assign the returned object to a dynamic typed variable then just call members on that directly.
There you have it.
This allows to load an assembly that your client proj doesnt have reference to in a seperate AppDomain and call members on it from client.
To test, I like to use the Modules window in Visual Studio. It will show you your client assembly domain and what all modules are loaded in that domain as well your new app domain and what assemblies or modules are loaded in that domain.
The key is to either make sure you code either derives MarshalByRefObject or is serializable.
`MarshalByRefObject will allow you to configure the lifetime of the domain its in. Example, say you want the domain to destroy if the proxy hasnt been called in 20 minutes.
I hope this helps.