December 23, 2010 4:22 PM by Daniel Chambers
Recently I’ve been writing a small WPF application for work where the goal is to be able to allow its users to download a single .exe file onto a server machine they are working on, use the executable, then delete it once they are done on that server. The servers in question have a strict software installation policy, so this application cannot have an installer and therefore must be as easy to ‘deploy’ as possible. Unfortunately, pretty much every .NET project is always going to reference some 3rd party assemblies that will be placed alongside the executable upon deploy. Microsoft has a tool called ILMerge that is capable of merging .NET assemblies together, except that it is unable to do so for WPF assemblies, since they contain XAML which contains baked in assembly references. While thinking about the issue, I supposed that I could’ve simply provided our users with a zip file that contained a folder with the WPF executable and its referenced assemblies inside it, and get them to extract that, go into the folder and find and run the executable, but that just felt dirty.
Searching around the internet found me a very useful post by an adventuring New Zealander, which introduced me to the idea of storing the referenced assemblies inside the WPF executable as embedded resources, and then using an assembly resolution hook to load the the assembly out of the resources and provide it to the CLR for use. Unfortunately, our swashbuckling New Zealander’s code didn’t work for my particular project, as it set up the assembly resolution hook after my application was trying to find its assemblies. He also didn’t mention a clean way of automatically including those referenced assemblies as resources, which I wanted as I didn’t want to be manually including my assemblies as resources in my project file. However, his blog post planted the seed of what is to come, so props to him for that.
I dug around in MSBuild and figured out a way of hooking off the normal build process and dynamically adding any project references that are going to be copied locally (ie copied into the bin directory, alongside the exe file) as resources to be embedded. This turned out to be quite simple (this code snippet should be added to your project file underneath where the standard Microsoft.CSharp.targets file is imported):
<Target Name="AfterResolveReferences"> <ItemGroup> <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'"> <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName> </EmbeddedResource> </ItemGroup> </Target>
Figure 1. Copy-Local references saved as embedded resources
The AfterResolveReferences target is a target defined by the normal build process, but deliberately left empty so you can override it and inject your own logic into the build. It happens after the ResolveAssemblyReference task is run; that task follows up your project references and determines their physical locations and other properties, and it just happens to output the ReferenceCopyLocalPaths item which contains the paths of all the assemblies that are copy-local assemblies. So our task above creates a new EmbeddedResource item for each of these paths, excluding all the paths that are not to .dll files (for example, the associated .pdb and .xml files). The name of the embedded resource (the LogicalName) is set to be the path and filename of the assembly file. Why the path and not just the filename, you ask? Well, some assemblies are put under subdirectories in your bin folder because they have the same file name, but differ in culture (for example, Microsoft.Expression.Interactions.resources.dll & System.Windows.Interactivity.resources.dll). If we didn’t include the path in the resource name, we would get conflicting resource names. The results of this MSBuild task can be seen in Figure 1.
Figure 2. Select your program entry point
Once I had all the copy-local assemblies stored safely inside the executable as embedded resources, I figured out a way of getting the assembly resolution hook hooked up before any WPF code starts (and therefore requiring my copy-local assemblies to be loaded before the hook is set up). Normally WPF applications contain an App.xaml file, which acts as a magic entry point to the application and launches the first window. However, the App.xaml isn’t actually that magical. If you look inside the obj folder in your project folder, you will find an App.g.cs file, which is generated from your App.xaml. It contains a normal “static void Main” C# entry point. So in order to get in before WPF, all you need to do is define your own entry point in a new class, do what you need to, then call the normal WPF entry-point and innocently act like nothing unusual has happened. (This will require you to change your project settings and specifically choose your application’s entry point (see Figure 2)). This is what my class looked like:
public class Program { [STAThreadAttribute] public static void Main() { App.Main(); } }
Don’t forget the STAThreadAttribute on Main; if you leave it out, your application will crash on startup. With this class in place, I was able to easily hook in my custom assembly loading code before the WPF code ran at all:
public class Program { [STAThreadAttribute] public static void Main() { AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly; App.Main(); } private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args) { Assembly executingAssembly = Assembly.GetExecutingAssembly(); AssemblyName assemblyName = new AssemblyName(args.Name); string path = assemblyName.Name + ".dll"; if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false) { path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path); } using (Stream stream = executingAssembly.GetManifestResourceStream(path)) { if (stream == null) return null; byte[] assemblyRawBytes = new byte[stream.Length]; stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length); return Assembly.Load(assemblyRawBytes); } } }
The code above registers for the AssemblyResolve event off of the current application domain. That event is fired when the CLR is unable to locate a referenced assembly and allows you to provide it with one. The code checks if the wanted assembly has a non-invariant culture and if it does, attempts to load it from the “subfolder” (really just a prefix on the resource name) named after the culture. This bit is what I assume .NET does when it looks for those assemblies normally, but I haven’t seen any documentation to confirm it, so keep an eye on that part’s behaviour when you use it. The code then goes on to load the assembly out of the resources and return it to the framework for use. This code is slightly improved from our daring New Zealander’s code (other than the culture behaviour) as it handles the case where the assembly can’t be found in the resources and simply returns null (after which your program will crash with an exception complaining about the missing assembly, which is a tad clearer than the NullReferenceException you would have got otherwise).
In conclusion, all these changes together mean you can simply hit build in your project and the necessary assemblies will be automatically included as resources in your executable to be pulled out at runtime and loaded by the assembly resolution hook. This means you can simply copy just your executable to any location without its associated referenced assemblies and it will run just fine.
Submit Comment | Comments RSS Feed
aweber1
May 13, 2011 12:35 PM
Hi Daniel,
This is incredibly helpful! I was recently looking for a way to do exactly what you've outlined and your method works great. All my referenced assemblies and code rolled up into one neat little package. Thank you for saving me a ton of time :)
--adam
brandon.shelton
May 17, 2011 3:52 PM
Perfect! ...exactly what I was needing. Great work.
June 02, 2011 8:00 PM
I am using this already after having read your tip. It seems that this can be used for vanilla WinForms and console applications also.
--aris
blog: http://www.level533.com
Tarun
July 06, 2011 6:24 AM
Hello, i just created my first wpf app in VS2010, i am not able to figure out where should i paste the target xaml code?(where standard Microsoft.CSharp.targets file is imported, didn't made any sense to me, pls provide a bit more detail)
July 25, 2011 3:51 PM
Daniel,
Thanks very much for posting this. I can't believe that creating a single-exe deployment was this simple - no external tools needed at all!
Sam
Gaurav
July 27, 2011 8:10 AM
Thanks for your article. This is amazing... worked like charm!
Simon Cropp
August 10, 2011 11:09 PM
Daniel
I would be interested on your opinon on this
http://code.google.com/p/costura/wiki/HowItWorksEmbedTask
It uses a similar approach to what you have described but does it in IL so it requires much less setup. ie you dont need to write the "AssemblyResolve" method or embedd any resources.
August 12, 2011 10:47 AM
Wow, that is really cool! Worked for me.
Tried ILMerge as well, did not work. This is really Cool. I will link to this! (So i can remember it for the next time ;))
All those paid solutions have nothing on this one.
August 22, 2011 9:25 PM
I have been using this successfully on 32 bit Win 7 and Win XP Pro. On 64 bit Win 7, this fails for release builds. However, if you decorate you Program.Main with [MethodImpl(MethodImplOptions.NoOptimization)] it magically works. You can also create a static constructor for your Program class and add the AssemblyResolve event handler there without having to use MethodImpl attribute.
This is curious.
aris
blog http://www.level533.com
Mack
September 08, 2011 12:11 PM
Note that this won't work if you have any mixed mode assemblies.
https://connect.microsoft.com/VisualStudio/feedback/details/97801/loading-mixed-assembly-with-assembly-load-byte-throw-exception-changed-behaviour
jyee
March 08, 2012 12:13 AM
I ran into a problem with this technique: http://stackoverflow.com/questions/9611154/assemblyresolve-event-fires-when-calling-assembly-loadbyte
jdege
October 31, 2012 8:01 PM
This was almost working for me. All but one of my dependent DLLs were loading properly. But there was one that was failing.
After a few hours of tearing my remaining hair, I realized that the problem was in Program.Main(). In it, you call App.Main().
In my application, App wasn't derived from Application, but was derived from my own base application class, which in turn derived from Application. And my base application class was defined in the DLL that was failing to load.
Why? Because the reference to App was resolved prior to the AssemblyResolve event handler being hooked up.
The solution was simple. I created a new class, gave it a static method that called App.Main(), and then it from within Program.Main(). With this change, App isn't referenced until the new method is called, which happens after the AssemblyResolve event handler is in place.
Destroer
January 10, 2013 12:49 PM
Thanks a lot. All work fine for me.
Rudi
February 12, 2013 11:47 AM
I ran into an issue with ILMerge when running on Win2008 server, and this simple solution solved it for me. Thank you for sharing this!
Rwinz Cyruz
March 26, 2013 4:16 PM
Thanks a lot. This is what I really need. Great work, Daniel.
Unknown (google)
April 25, 2013 12:44 PM
You are full of win.
Arun Sharma
October 20, 2013 3:57 PM
Just awesome \m/.
Thanx :)
Sam Pearson
October 23, 2013 4:11 PM
Does not appear to work if the embedded assembly is a Portable Class Library. Any thoughts? SO question here: http://stackoverflow.com/questions/18793959/filenotfoundexception-when-trying-to-load-autofac-as-an-embedded-assembly
Steve
July 14, 2014 5:47 PM
Hello
very nice tutorial, I am failing to implement it because (I think) I can't find where the XAML code should be copied to.
I am using VS express 2013.
My program's got 2 DLL's, but one of them is called from the XAML and that's where I am getting the error. So I need to find a way to call that DLL so that it finds it!
Thanks
Steve
wizkid
November 03, 2014 11:28 AM
Hello
What about the application config file? It contains service endpoint definitions, connection strings and such. I tried setting it as "Embedded Resource", but that doesn't work.
wizkid
November 03, 2014 11:42 AM
Also, this might be an amateur question, but using a decompiler, the embedded assemblies do not show any source code at all. Is this a reliable obfuscation technique as well? Whoa..if it is, this is amazing!!