Update: My first solution doesn't work for assemblies contained in the resources of a program like OP asked; instead it loads it from the disk. The solution for loading from a byte array will follow (in progress). Note that the following points apply to both solutions:
As the Environment.Exit()
method throws an exception because of lack of permissions, execution of the method will not continue after it is encountered.
You're going to need all the permissions that your Main method needs, but you can find those quickly by just typing in "Permission" in intellisense, or by checking the SecurityException
's TargetSite
property (which is an instance of MethodBase
and will tell you which method failed).
If another method in your Main needs the UnmanagedCode
permission, you're out of luck, at least using this solution.
Note that I found that the UnmanagedCode
permission was the one that Environment.Exit()
needs purely by trial and error.
Solution 1: When the assembly is on the disk
Okay, here's what I've found so far, bear with me. We're going to create a sandboxed AppDomain:
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1
PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do
permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.
permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method
var assembly = Assembly.LoadFile(exePath); // path to ConsoleApplication1.exe
var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain
try
{
domain.ExecuteAssemblyByName(assembly.GetName(), new string[] { });
}
// The SecurityException is thrown by Environment.Exit() not being able to run
catch (SecurityException e) when (e.TargetSite == typeof(Environment).GetMethod("Exit"))
{
Console.WriteLine("Tried to run Exit");
}
catch (SecurityException e)
{
// Some other action in your method needs SecurityPermissionFlag.UnmanagedCode to run,
// or the PermissionSet is missing some other permission
}
catch
{
Console.WriteLine("Something else failed in ConsoleApplication1.exe's main...");
}
Solution 2: When the assembly is a byte array
Warning: cancerous solution follows.
When changing my solution to load a byte array, OP and I discovered a weird exception file not found exception: even if you pass in a byte array to Assembly.Load()
, domain.ExecuteAssemblyByName()
still searches the disk for the assembly, for some weird reason. Apparently we weren't the only ones with the issue: Loading Byte Array Assembly.
First, we have a Helper
class:
public class Helper : MarshalByRefObject
{
public void LoadAssembly(Byte[] data)
{
var a = Assembly.Load(data);
a.EntryPoint.Invoke(null, null);
}
}
which as you can see, loads the assembly using Assembly.Load()
and calls it's entry point. This is the code we'll be loading into the AppDomain
:
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
// This is where the main executable resides. For more info on this, see "Remarks" in
// https://msdn.microsoft.com/en-us/library/system.appdomainsetup.applicationbase(v=vs.110).aspx#Anchor_1
PermissionSet permission = new PermissionSet(PermissionState.None);
// Permissions of the AppDomain/what it can do
permission.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags & ~SecurityPermissionFlag.UnmanagedCode));
// All SecurityPermission flags EXCEPT UnmanagedCode, which is required by Environment.Exit()
// BUT the assembly needs SecurityPermissionFlag.Execution to be run;
// otherwise you'll get an exception.
permission.AddPermission(new FileIOPermission(PermissionState.Unrestricted));
permission.AddPermission(new UIPermission(PermissionState.Unrestricted));
// the above two are for Console.WriteLine() to run, which is what I had in the Main method
var domain = AppDomain.CreateDomain("SomeGenericName", null, adSetup, permission, null); // sandboxed AppDomain
try
{
Helper helper = (Helper)domain.CreateInstanceAndUnwrap(typeof(Helper).Assembly.FullName, typeof(Helper).FullName);
// create an instance of Helper in the new AppDomain
helper.LoadAssembly(bytes); // bytes is the in-memory assembly
}
catch (TargetInvocationException e) when (e.InnerException.GetType() == typeof(SecurityException))
{
Console.WriteLine("some kind of permissions issue here");
}
catch (Exception e)
{
Console.WriteLine("Something else failed in ConsoleApplication1.exe's main... " + e.Message);
}
Note that in the second solution, the SecurityException
becomes a TargetInvocationException
with it's InnerException
property being a SecurityException
. Unfortunately, this means that you cannot use e.TargetSite
to see which method threw the exception.
Conclusion/Things to keep in mind
This solution isn't perfect. It would be much better to somehow go through the IL of the method and artificially remove the call to Environment.Exit()
.