Handling Entry Assemblies that Won't Load: Method 1

Last week, when I posted about failing to run in partial trust gracefully, the method I showed only worked if your main assembly could be loaded.  However, if it has a minimum permission request that cannot be satisfied, your main method won't ever be called, and you won't be able to fail gracefully.

For instance, the following code:

using System;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;

[assembly: PermissionSet(SecurityAction.RequestMinimum, Unrestricted = true)]

public class Test
{
    public static void Main()
    {
        Console.WriteLine("Hello World");
        return;
    }
}

If I compile this code to main.exe, and run it from a partial trust environment, the application bombs out with the following exception:

Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'main, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Failed to grant minimum permission requests. (Exception from HRESULT: 0x80131417 (CORSEC_E_MIN_GRANT_FAIL))File name: 'main, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' ---> System.Security.Policy.PolicyException: Required permissions cannot be acquired.

As I said above, this exception is thrown before Main ever gets to execute, so there's no way to setup a handler for this exception.  So how can you display a nicer error to your users?

One easy way is to create a shim application that will attempt to load your entry point for you.  Since your shim application won't have the declarative request on it, it should be able to load.  And once it's loaded, we can simply catch the FileLoadException that the entry point is throwing, and recover by showing the error message to the user.

We'll want to be careful however, that we don't catch every FileLoadException, since we don't want to display our message if some other assembly happens to fail to load.  One simple shim application would look something like this:

using System;
using System.IO;
using System.Reflection;

public class Shim
{
    public const int FailCode = 0;

    public static int Main()
    {
        try
        {
            return AppDomain.CurrentDomain.ExecuteAssembly("main.exe");
        }
        catch(FileLoadException e)
        {
            try
            {
                AssemblyName failedAssembly = new AssemblyName(e.FileName);
                if(failedAssembly.Name == "main")
                {
                    Console.WriteLine("Could not load main.exe, possibly due to security issues. Please copy to a local location.");
                    return FailCode;
                }
            }
            catch(ArgumentException) { /* do nothing ... this wasn't an assembly name */ }

            // if we got here, then we didn't fail to load the main assembly, so let the exception propigate on
            throw;
        }
    }
}

Now, instead of running main.exe, I run shim.exe instead.  The shim wraps main.exe with a try...catch block, which handles the exception that occurs when main fails to load.  The throw clause rethrows the exception if it wasn't a failure to load the main assembly, so that any installed unhandled exception filters (or a debugger if there are none) will get called.

There are a few problems with this approach.  Assembly.GetEntryAssembly() will return shim.exe instead of main.exe, the shim application will be at the root of every call stack, and you have to ensure that your users call the shim instead of the main application.

Tomorrow, we'll try to address some of the inadequacies of this method, and solve the problem in a better way.