My .NET focused coding blog.

Single-file executables in .NET Core 3

The new PublishSingleFile option in .NET Core 3 lets you package an application into a single executable (.exe) file that contains all assemblies, resources, content files and other stuff that the app requires at runtime. This means that the output directory of an app that previously would contain a bunch of framework specific and referenced DLLs, configuration files and other content can now be reduced to contain only a single .exe file that you can simply double-click on to run the app. These single-file executables do however come with some gotchas.

Startup

The first one is the initial startup time. Create a new WPF application that targets .NET Core using the dotnet new wpf command (or use the WPF App (.NET Core) template in Visual Studio 2019) and publish it using the dotnet publish command with the PublishSingleFile option and a required runtime identifier:

dotnet publish -r win10-x86 -c release /p:PublishSingleFile=true

If you then click on the .exe in the publish folder to start the app, you’ll notice that it takes a few seconds before the main window shows up. The publish command uses a bundling tool that embeds the app and its dependencies into the native .exe. When you run the .exe for the first time, it extracts the embedded files into a temporary folder and runs the published app from there. The extracting and copying of the files take some notable time. If you run the same .exe again, it will use the already existing files in the same temp folder and start a lot faster.

Configuration

You can extract a file from being included in the executable bundle by adding an <ExcludeFromSingleFile> element to the project (.csproj) file. You may be tempted to do this for configuration files that contain settings that you want to be able to change without recompiling the app:

 
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
 
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
 
  <ItemGroup>
    <None Update="WpfApp1.dll.config">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
    </None>
  </ItemGroup>
 
</Project>

An excluded file will be deployed next to the .exe in the publish folder when you publish the app:

Unfortunately, the extracted app in the temp folder won’t be able to find it at runtime unless you copy it into the same temp folder somehow. The native .exe won’t to this for you. Instead of extracting this kind of files, you probably want to include them in the bundle as they are and then modify them in the temp folder when needed. You can get the location of the temp folder by calling System.AppContext.BaseDirectory when you run the single-file .exe. On my machine it’s something like %Temp%\.net\WpfApp1\i45xbufo.jsx\ where WpfApp1 is the name of the .exe.

You can exclude the .pdb debugger file that gets created by default by using the /p:DebugType=None option when you publish, or by adding a <DebugType> element to the .csproj file:

 
<PropertyGroup>
  <DebugType>None</DebugType>
</PropertyGroup>

Size

If you look at the size of the .exe in the publish folder, you’ll notice that it’s somewhere around 140MB for a WPF application created using the default template without any additional references or source code. There is a <PublishTrimmed> setting that you can add to the project file to decrease the file size:

<PropertyGroup>
  <PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>

It enables an IL linker tool in the .NET Core SDK that removes any unused assemblies from the bundle. This reduces the file size to about 80MB in this trivial case and the rather simple sample application still works as expected. In a real-world scenario, you should use this option with caution though as the linker tool may not be able to detect dependencies that are loaded dynamically. Trading some megabytes of disk space for a more unpredictable runtime behaviour is usually not a good idea.

MSIX

If you are looking for a way to distribute your app to your end users, depending on your requirements, packaging it as an MSIX may be a better option than creating a single-file .exe. Whereas the PublishSingleFile option basically gets you a self-extracting ZIP file that contains all your app’s dependencies, MSIX provides clean and reliable Windows integrated installs and uninstalls of apps. I’ve written an article in the MSDN Magazine that demonstrates not only how to package apps, but also how to set up continuous integration (CI), continuous deployment (CD) and automatic updates for sideloaded MSIX packages.


One Comment on “Single-file executables in .NET Core 3”

  1. Sygmond says:

    Nice blog post! I have one issue and I can’t find a solution for it. Every build of single-file exe is creating a new temp folder on first run. That’s a problem when I use the Settings.Settings file (user.config) and I want to Upgrade the settings from an old version. This problem only occur in single-file exe. New versions are not anymore in the same folder side by side, because every build is creating the new folder version in a new random generated folder in temp. i.e. instead of having folders like 1.0.0.0, 1.0.0.1, 1.0.0.2 in the same folder, now I have them under different folders, i.e. AppName_Url_ufiidap05zqqwmq2ozxel2m2ue23ontq/1.0.0.0, AppName_Url_xsm0hazv5l40kxyomr0w0scagzmdrpgy/1.0.0.1, etc. I hope I explaind well my issue. Thank you!


Leave a comment