The basic idea as well as much of the code itself come from Costura.Fody, an add-in to Fody, which has the same effect. We mostly differ only in the architecture we use to apply our changes to the build process. Like Costura, Packer is released under MIT.
You can find instructions on how to add Packer to your project on the Packer GitHub page.
When I first encountered Costura.Fody, I was amazed. Before I discovered it, I needed to use an installer or complex tools like ILMerge/ILRepack, but Costura.Fody proved that you can have a self-contained .exe by adding a NuGet package, and it just works. So now we have that, too.
Why should I distribute my application as a single .exe file?
When you distribute an application, you want to make it as easy as possible for the end-user to download and start your application. The easier this process is, the more likely the user is to run your application.
Traditionally on Windows, you distribute applications using installers. But installers are heavyweight and represent a barrier themselves: users may not be as willing to install your application, especially if it’s small, but they may be more willing to just run it.
Another common way to distribute an application is as a .zip file that the user needs to extract. But in this case, the user needs to have enough computer skills to know that they must extract rather than run directly from the .zip file (in which case your project would probably silently crash) and they must be able to identify the correct .exe file to run from possibly many files that are in the .zip file. This is a problem especially because the .dll files that you depend on are normally all in the same directory as your main .exe file.
Updating is also harder with a .zip file where the user must remember where they kept your application when they extract the update... and you’d better hope that the possible extra files that are no longer required don’t mess the application up.
Finally, there’s the matter of aesthetics. A single file just looks so much cleaner than a folder with the main .exe file hiding among dozens of libraries!
Distributing your application as single .exe file allows the user to download only a small file and immediately run it. It doesn’t clutter their computer as an installer would, and it doesn’t have the risk that they won’t be able to open it as a .zip file would.
Can’t .NET Core 3 already do this on its own?
That is true. However, this would require you to use .NET Core.
Since .NET Framework is installed on every Windows machine, you can expect your small single-file .exe program to work everywhere. (If you want to be sure about older versions of Windows, you need to choose an older version of .NET Framework.) This means that your final .exe file can be even 1 MB or 2 MB in size. What is more, our telemetry data shows that, despite the hype .NET Core and .NET 5 are receiving, a majority of developers still target .NET Framework.
On the other hand, with .NET Core 3, you cannot usually expect the end-user to have .NET Core installed on their machine, and so you must include the runtime with your application, which means an extra 30–70 MB that the user needs to download and that can’t be shared among applications.
How does Packer work?
Packer has two components: one works at build time and one at runtime.
At build time, Packer runs in the build process after the main compilation completes. It reads from the disk all assemblies that you have set as “Copy to output folder” which includes most referenced assemblies outside the .NET Framework itself. It also includes assemblies from NuGet packages. Then it puts the content of those assemblies as resources into your .exe file, optionally compressed.
At runtime, Packer registers itself as an assembly loader so that whenever the .NET Framework runtime fails to load a required assembly, it gives a chance to Packer to load it, and Packer will then uncompress the data in your application’s resources and return that as the required assembly to the runtime.
How can I add Packer to my project?
It is very easy:
- Install the NuGet package PostSharp.Community.Packer.
- Add [assembly: Packer] somewhere in your code.
Then build your project and to test that everything is working, copy your resulting .exe file into a standalone folder and run it.
Personal note
At work, I often develop small tools to make life easier for other developers. I found that even there, among tech-savvy colleagues, distribution makes a big difference. Few colleagues would be willing to build a productivity tool from source, but many more would download and run a single .exe or .jar file (depending on the company’s preferred language). Updates – manual or automatic – get easier as well.
Tools like PostSharp.Community.Packer truly extend your program’s reach.