Say I have a simple “Hello World” console application that I want to send to a friend to run. The friend doesn’t have .NET Core installed so I know I need to build a self contained application for him. Easy, I just run the following command in my project directory :
dotnet publish -r win-x64 -c Release --self-contained
Pretty self explanatory :
- Publish the project from the current directory
- Build the project to run on Windows 64 bit machines
- Build in release configuration mode
- Publish everything as “self-contained” so that everything required to run the app is packaged up with our executable
So this works right, we end up with a folder that has our exe and everything that is required to run it, but the issue is that there is a tonne required to run even a HelloWorld console app.
All up it’s a little over 200 files. Crazy – but it makes sense. It’s essentially having to package the .NET Core runtime just to run Hello World.
So functionally, this works. But optics wise, it looks like a mess. I’ve sent folders like this to clients and had to say “uhh.. So I’m going to send you a folder with hundreds of files in it… But… Can you just find the one titled MyApplication.exe and run that and don’t worry about the rest?”. When people are used to having an icon on their desktop they double click and things just.. work… This just doesn’t cut it.
You Need .NET Core 3.0 (Preview 5+)
I need to put that in bold because this will not work if you don’t have .NET Core 3.0. And if you are an early adopter (At the time of this post .NET Core 3.0 is not GA yet), you will need atleast preview 5 upwards. If you’re not one to mess around in preview’s, then you can just wait a few months till .NET Core 3.0 is released!
The PublishSingleFile Flag
All that intro and it literally comes down to a single command flag :
dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true
All this does is runs our publish command but tells it to package it within a single file. You’ll notice that we no longer specify the self-contained flag. That’s because it’s assumed that if you are packaging as a single exe, that you will want all it’s dependencies along with it. Makes sense.
And the output :
A single tidy exe! When this is executed, the dependencies are extracted to a temporary directory and then everything is ran from there. It’s essentially a zip of our previous publish folder! I’ve had a few plays around with it and honestly, it just works. There is nothing more to say about it. It just works.
Helpful Tip : Make sure you clean your publish directory or just outright delete your bin folder. It doesn’t break anything to not do so, but all those old DLL’s just hang around until you do so your nice single EXE is hard to spot.
File Size And Startup Cost
Keen eyes will notice something about the above screenshot. The file size. It’s over 70MB! That’s crazy for an application that does nothing but print Hello World to the screen! This is solved in Preview 6 of .NET Core 3.0 with a feature called IL Linker or Publish trimmer that omits DLL’s that aren’t used. You can read more about that here!
The other issue you may find is that there is a slight startup cost when running the self contained executable for the first time. Because it needs to essentially unzip all dependencies to a temporary directory on first run, that’s going to take a little bit of time to complete. It’s not crazy (5 seconds or so), but it’s noticeable. Luckily on subsequent runs it uses this already unzipped temp folder and so startup is immediate.
Wade,
Do you know where does the temp directory get created? We have been running into problems of missing dependencies and unnecessarily modified or removed files from the installation folder because customers didn’t know. This approach will help us considerably. However if the temp directory location is locked down, which it is via group policies on Enterprise networks, then this would become another battle. Similar to ClickOnce application. Anyway, I was just curious about it after reading your post. Very informative post.
Honestly. No. And I did have a quick search for it both on my PC and in all documentation. It says “A” Temp directory but not “THE” Temp directory which made me think it will be something in your user profile or similar. I’ll keep looking and let you know.
Fyi, I found the temp directory is under my user folder at below:
C:\Users\[my login]\AppData\Local\Temp\.net
The easiest way to figure out these kind of details is by using procmon from sysinternals (basically dtrace for Windows)
Run it as admin, then start the program and check the file open kernel calls.
If you put this line of code in your Hello World program:
Console.WriteLine(Assembly.GetCallingAssembly().CodeBase);
Console.ReadLine();
And publish that into a single executable. It will tell you where it runs with all the dlls that the program is referencing to
Thanks for writing this up. Super helpful. I struggled for hours with dotnet-warp and fody, presumably due to compatibility issues because I’m on the preview dotnetcore sdk. This just worked, first shot. Yeah, the binary is enormous, but that too will be solved. This bodes well for the future of .net core on the desktop.
Hey Ben,
You may want to check out the latest post (https://dotnetcoretutorials.com/2019/06/27/the-publishtrimmed-flag-with-il-linker/) which talks about how to trim down the size of the exe with no hassles at all!
Awesome @WADE,
Really nicely explained and cool.
Thanks much
Hi Wade
Thanks for writing the article. This is very very useful. However, when I try to run the exe I get the following error: “The application to execute does not exist: myapp.dll”. Did you encountered this as well? And if so, how you solved it?
Download fody
From nuget and
Just click to build
Best and most simple way to create single exe file from visual studio
I just tried this and can’t get my publish command to do both SingleFile as well as ReadyToRun.
The only way I can get’em both, is if I include these settings in the project file.
I can easily see whether the files got SingleFiled or not, but for determining whether they were also ReadyOrRunned, I simply look at the resulting .exe file size.
When publishing SingleFile via the command line, my .exe stays the same size as when I only SingleFile, but don’t ReadyToRun, in my project file.
When I SingleFile and ReadyToRun in my project file, the .exe becomes a lot bigger. I need to get it the same size from the command line, so I’ll know it worked.
I tried explicitly specifying the –self-contained param after the -p:PublishSingleFile=true param. It still doesn’t work.
Also, the SingleFile/ReadyToRun compilation was generating .exe files a few MB larger than it is now, even though I didn’t change any code since then. That’s pretty strange, since compilation is supposed to be a fairly deterministic operation, aside from the occasional datetime stamp here and there.
Anybody else experience this?
More interestingly it would be useful to determine where the location of the bundle exe resides. I haven’t yet found a way to do this and it doesn’t appear to be documented. If you could do this you can pack your appsettings.config external to the bundle exe, and load it from there instead of the temp folder location. No one wants to explore for a temp folder to edit appsettings.config, that’s just terrible for end user configuration.
Is it possible to keep Configuration File outside from combined exe ?
When I run the single exe on Windows 2008 R2, it prompts that api-ms-win-crt-runtime-l1-1-0.dll is mssing
Neat! Will this protect against reversing tools like ILSpy and JustDecomplie?
It would make it “harder” but it wouldn’t protect you. Your code is still there, just inside a different bundle. Really only code obfuscation can help you if you really don’t want someone to decompile (Or maybe your DRM as complex as possible).
Wow. I guess I’ll just stick with the old .Net Framework whenever the target audience is Windows.
I was recently explored this single exe solution and it was a decent experience, however after I updated the version of a extension, it start giving me issues where when I compiled a win-64 version, which is the version I publish since start of the application, it will deploy the older version of dependencies and prompt error when I run my application, but when I deploy with win-86 version, I have not encounter any similar issue. Not sure anyone encounter similar issue?
Over 200 files and over 70 MB for a “Hello World” application. Wow, the future is here *laugh*
What exactly is the point of essentially shipping the whole .net framework with every application instead of simply having it installed as part of the operating system? MS should stop chasing the hip crowd and stop trying to cater to the nodeJs kids.
The .NET Framework is a brilliant piece of engineering, sad to see its name and reputation being ruined and tarnished by this .core crap. Top engineers like Eric Lippert and others responsible for .NET have long moved on and good-for-nothing talking heads like Scott Hanselmann have taken over. No wonder .NET has been going down the drain ever since.
This saved me from a lot of headaches. Thank you very much!
Thank you for the great posting.
I was told on Feb 9,2021 (yesterday) that Microsoft have the single file publish which does not require end-user to have .net installed on their PC to run a .net app (https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file).
However, I built a simple Hello world vb.net app (Windows Form .net visual basic template) using 2019 visual studio and .net 5.0, if I publish the app as a single file in a folder of my own PC as instructed by the above web link, the .exe cannot be run. If I publish the app not as a single file, it runs well. I still cannot figure out the root cause. The difference between your publish and mine is you run a dotnet command, and I used Visual Studio 2019 publish from build tab.
Wade, do you have any idea why my published single file cannot be run?
My hello world has a Form1, a Button1 and a Texbox1. This is my vb code, no imports to make it super clear:
For now, just try using dotnet commands. The first thing I would do is try the above with a console application. You are using a WinForms app by the looks of it which I’m not 100% sure will work. Atleast when this was written, WinForms were not cross platform.
dotnet publish -r win-x64 /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true –self-contained true