My blog about software development on the Microsoft® stack.

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.



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s