So, it has come time to post my first (potentially last) article on CodeProject. Depending on how this goes, whether I am laughed out of the market or there is interest, there could be a follow up. I am going to be clear up front. There is no code sample in this intro article, therefore it is more of an informational article for anyone who wants to know. Secondly, I have not worked out all of the kinks yet myself; however, the theory is sound. All that this requires is more testing and a small C++ Win32 shim to get started.
Without further ado... let's state what this is about: A "standalone" .NET exe file. I put quotes around standalone, but the file won't actually be able to run by itself. If Windows allowed for in memory loading of complex DLLs from resources, then this might make the exe truly standalone, but alas, Microsoft for some very "good" reasons do not allow the developer to do such a thing.
What I wanted to do was to run a custom branded WPF program on a system running Windows. A minimum of XP is assumed. Since we are running XP, we have to assume that there is no .NET Framework installed.
Now you may be thinking to yourself: "Virtualization! Virtualization!", and while yes, that is an option, this option got ruled out fairly quickly. Thus there is no virtualization voodoo happening in this article.
So like I said.. a custom branded WPF application without installing the .NET Framework. Some people that I discussed this with said: "HERESY!" and for good reasons. Multiple articles and questions posted online said it was not possible. To all these people, I say it is in fact possible with some catches. Let's discuss some options.
When I first started strategizing how to do this, I ran into my first bout with Win32 hooking. This seemed like a good method. Modify the DLL tables, or maybe just hook the required DLLs in process using a CLR host. Then one realizes that hooking is not the do all end all for certain things. Hooking has its ups, but it also has its downs. Like for instance... it is not a 100% rock solid solution. You have to put your tongue at the right angle and hope that in 100% of your test cases, after performing the hooking magic, your solution works. Hooking won't catch 100% of the cases no matter how hard you try in such a complex scenario as this. After about two days of pouring my mind into figuring out how to do this with hooking, this option went off the table.
Next we come to virtualization technologies. For instance, we can look at Boxed app, thin app, spoon studio, and the like. These all seemingly allow you to pack the .NET Framework into a single exe and distribute it and it will just work. There are two issues with this option. The first is stability. Since this method is unsupported, it can be unstable. After taking a look at Boxed app's forums, one of the first posts that I saw was "hey.. my app doesn't work in this scenario". I then looked at how Boxed app does its magic: Hooking! That one liner made me throw Boxed app off the table of options because of the aforementioned reason above about test cases. It turns out spoon studio does the same thing. These virtualization options essentially make a virtual file system on the media and then hook all calls to the Windows registry and file system. Sure it apparently works... They sell the products, but that is not good enough to get my blessing. The second option is that these solutions are not free. Spoon studio costs an arm and a leg. While boxed app is more affordable.. it's not free. I won't be shelling out money for a solution that relies on hooking.
Let's now take a step back and review the actual requirements of what I was trying to do. In my company, we have to ship an application developed in .NET, because that is the easiest, to a manufacturing plant. The people at the plant run the program. These people are computer illiterate and introducing any sort of step outside a few mouse clicks is out of the question. The computers are running a minimum of XP, so we then have to assume no .NET Framework. Lastly, the second they click on the program, everything should be custom branded in regards to UI, even if the process needs to install a program. We want to run some custom program routines before we install anything to the system as well. With that, let's go over yet some more options on how we could do this.
Another option of doing this is to use Mono. We could write a program using GTK and pack it using mkbundle as a standalone program. The program then runs our custom routines and installs a real program that we want the manufacturing line to use. I initially tried this option. I downloaded xamarin studio and gave it my all. I then realized that xamarin studio fails in comparison to Visual Studio, in my opinion. IDEs aside, after using and loving WPF for so long, GTK felt like taking 10 steps backwards. For the custom UI we wanted to do, it would be hard to do using GTK. We would have to draw primitives ourselves, such as lines and what not. On top of that, GTK doesn't look like it supports device independent pixels in drawing the primitives, so that was another nail in the coffin for this option. The real kicker to this was that mkbundle is essentially broken on Windows. I ended up posting on the mono forums with the issues that I had to no avail. mkbundle has issues with spaces in command line arguments and also is still trying to use an unsupported -gcc-cygwin command line option to the compiler. Very unprofessional and rookie mistakes that have been there for months and months, in my opinion. Luckily, you can pack all required mono DLLs into a simple directory structure with a lib and a root folder. Since mono does not rely on the Windows registry at all to run, it will find the required dlls in the directory very easily since that is what it uses to search with. The application will just run.
The last option I thought of was to have a front end using mono by packing the required DLLs and to install Silverlight in the background. I would then be able to use the power of WPF via Silverlight and run the custom routines / installs. This option is good until you realize that there is too much stuff going on just to get something simple accomplished.
I bet many of you are now thinking "Yeah... just write the darn program using an available UI toolkit." I would agree as an outsider, it seems like I went to many lengths just to get something simple done. BUT, it is with this struggling that I figured out how to run the .NET Framework from any given directory as a small redistributable. Also, I love WPF so much that when I am writing desktop apps, any other UI toolkit is out of the question. I have tried several and nothing really compares in my mind.
So here is the interesting part. "Just tell us already!"
I tried running the .NET framework a few times using this method initially and it failed on anything other than a simple command line program. It turns out that I was missing a second piece of the puzzle in the windows registry. After installing / uninstalling the .NET framework about 50 times, I have finally accomplished what I came for.
What I did is I made a simple win32 (x86) WPF application on my Windows 7 machine and brought it over to an XP test machine to test the theory out. The WPF application is simple. There is just a button that when clicked displays a messagebox.
Anyhow... a .NET exe looks in the registry when it starts to figure out the install paths for the framework. Obvious right? Just flip one of those keys around and we are cruisin! Yes well.. it turns out that you have to flip 3 or 4 of those paths around to get cruisin. When your .NET exe runs, the two major registry nodes are the HKLM......software\microsoft\.NETFramework node and the software\microsoft\.NETFrameworkSetup node. The exe first looks at the .NETFramework node to get an initial path to the framework and then later looks at the frameworksetup node. Therefore you need both. If you browse these nodes, you can easily see all the string registry keys that list out an "InstallPath" or any sort of path at all. If you remap these all to a custom directory that has the framework, that directory will then be used to run the framework. Overall, I believe there are 5 keys that need to be modified. Most are install paths, but there are a few that WPF uses as well. The entire .NET folder is normally under C:\Windows\Microsoft .NET or something similar. From this, you can begin to build the redistributable.
Next, after several days of probing with a nicely made piece of software called APIMonitor, I was able to get all the missing native dlls required to run the framework. If you run the framework from a custom directory, the framework, for some reason, wants mscorwks.dll. Put that in the framework\v4.0.30319 directory. I am not sure about the exact list, but the framework also requires mscoree, mscoreeis, msvc0100_0400clr, and mscorrc.dll. For now, I just plopped those in the system32 directory so that the exe could easily find them. If you package the actual WPF app in the same directory as these required native dlls, I believe the framework will find them. Windows seems to use the current exe directory as one of the search paths to locate DLLs natively, but I have not tried this yet.
Next after all the native DLLs get loaded in the framework, the CLR starts asking you for managed DLLs such as presentationcore, presentationframework, etc.. Another bit of interesting information. How does the CLR find these DLLs? I am sure countless articles describe it online, but simply put, the CLR uses the registry every time it needs to find a managed DLL that is shipped with the framework: the GAC if you will. So rather than figure out how the mapping worked and getting every single key correct in the registry to map all the managed DLLs I needed for my redistributable, I remembered something interesting from awhile ago. The CLR looks in the same directory as your managed exe for DLLs that it cannot find before throwing an assembly resolve event in the app domain. There are two things we can do. We can either package the required DLLs as resources and pull them out when the event is thrown, or just throw them in the same directory as the exe. I chose the latter just to get up and running. This means that if the CLR cannot find GAC assemblies, it will fall back to looking at the current exe directory for the assemblies.
Here is the sequence of events I have devised by compiling all of this information. Let's assume there is no .NET framework on the system. Either way a small Win32 C++ shim will have to be written to use this method.
- We package our small .NET redistributable with the Win32 shim as a resource along with 7zip and anything else required.
- The exe is run. The shim realizes .NET is not installed. It creates a temp directory
- The shim unpacks a standalone version of 7z and our zipped redistributable. Then we unzip the contents of the redistributable.
- We save any values needed in the registry using the Win32 API. For instance if .NET 3.5 is installed, but we need .NET 4. We save the .NETFramework node and .NETFrameworkSetup node to the temp folder.
- We then write our own registry values in the registry using known values. These can be .reg files packaged as resources in the shim. The registry files contain the known .NETFramework and .NETFrameworkSetup nodes with our remapped installpaths to the temp directory
- We then run the WPF application that we unpacked with the redistributable.
- When the WPF app runs, the first thing it does is pinvoke to the Win32 API to delete our registry keys we wrote in step 5. It then restores the previous registry value files that we saved in step 4 to the temp directory. This works because after our window is shown, the CLR no longer needs the registry. You also want to do this step so that you don't hose any previous or future 'official' install. Turns out that if the .NET installer sees the nodes already there in the registry when it installs, it will not modify the installPath key in the .NETFramework node. Therefore if you leave your custom directory key installed and delete your directory later, your official install will be hosed. Stupid Microsoft.. Just overwrite all keys in the Registry. I cannot fathom why the framework installer does not do this.
- The WPF app then does its custom stuff and you have a standalone framework package.
Points of Interest
- The shim needs to account for the proper registry nodes depending on if the system is 32 or 64 bit: wow6432.
- I am uncertain if the registry keys are not there for the CLR to search for when it pulls in assemblies what will happen. I assume it will just use the fallback directory method like I described above when searching for the assembly. Remember all assemblies are not pulled in at the beginning. It is only when you use a type in a given assembly that the CLR pulls it in.
Once I got this working with the base Microsoft .NET framework folder, I proceeded to gut all of the things not needed inside the folder to reduce the size to the smallest redistributable. Since all the managed assemblies that are needed are packaged in the same directory as the exe itself, you can axe the entire assemblies directory. It is very obvious what you can keep and what can go. Any configuration files can go. Pretty much anything that is not a DLL can go. You will want to keep the font files just in case WPF needs those. Any assemblies in the framework folder that you do not use can be deleted. Additionally, believe it or not about 200MB of that framework folder is the setup cache. That can go. When you do a repair or reinstall the framework, it uses the files from that folder to repair the install.
After gutting the Microsoft .NET folder and including all needed assemblies and registry files for WPF to run, I came out with a folder just shy of 50 MB. One might even be able to cut this down even more. After zipping it up with ultra compression using 7zip, the folder went down to around 12 MB. That's right.. the redistributable is 12 MB zipped and around 50 MB unzipped. Of course if you need to use anything other than PresentationFramework / Core, System, system.xaml, system.xml, or windowsbase, those managed assemblies will need to be packaged as well. I packaged everything needed for a base WPF window, nothing more.
Overall giving some time estimates of the steps at the bottom of the implementation section, I have estimated that it will take 3 - 6 seconds from the time the user clicks on the exe to when the first WPF window will appear. Again that is just an estimate.
If there are any questions or is any interest in this article, I can write a follow up with a sample shim and the redistributable package that I myself would be using.
- 8/4/2013: Article posted.