Single-File Distribution of Windows Apps - Single Binaries and the Limits of OS Dependencies

· Updated: · · Windows, Deployment, Single Binary, .NET, C++, WebView2, WinUI

Revision history (1 updates, last updated Sep 1, 2026)

A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.

Retranslated as a full translation of the Japanese original. The previous English version was an abridgement that carried only part of the source, so sections, tables, Mermaid diagrams, figure captions and FAQ entries were missing. All of them have been restored to match the Japanese original, and the technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.21614554)
First published
Cite this article(DOI: 10.5281/zenodo.21614553)

This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.

Go Komura (2026). Single-File Distribution of Windows Apps - Single Binaries and the Limits of OS Dependencies. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21614553 https://comcomponent.com/en/blog/2026/03/19/003-windows-single-binary-and-os-dependencies/

DOI (latest version)
10.5281/zenodo.21614553
DOI (this version)
10.5281/zenodo.22217180

This article started from a casual conversation about how far you can go on Windows before something stops counting as a single binary. The post that set it off is below. You can skip it; everything after it reads on its own.

On Windows, “we’d like to ship this as a single file if possible” is a very ordinary request. For internal tools, equipment-integration tools, monitoring terminals, offline environments, and sites that want to avoid installers as much as possible, going single-binary is very appealing.

However, if you do not untangle this conversation at the start, it usually stops making sense partway through. That is because “we want a single binary” on Windows tends to mix four separate concerns.

  • We want a single distribution artifact
  • We want to avoid pre-installing the .NET or Visual C++ runtimes
  • We want it to run by just dropping it in place, with no installer and no administrator rights
  • We do not want to depend on differences between target Windows versions

These four are not the same thing. In practice, this framing keeps everyone aligned:

You can get quite far toward shipping a single EXE. But you cannot reduce the dependency on the target Windows to zero.

This article maps out that boundary for practical Windows app work.

A single EXE does not remove OS dependenciesA diagram showing that the wish for a single binary mixes distribution concerns with dependency concerns, and that while consolidating the distribution into one EXE is largely achievable, the dependency on the target Windows cannot be reduced to zero.We want a single binaryDistribution: one artifact, drop-in deploymentDependency: no runtime needed, no OS dependencyConsolidating into one EXE is largely achievableOS dependency cannot be reduced to zero

Figure 1: The wish to ship one file mixes a feasible goal with an impossible one.

1. The Conclusion First

Summarizing the conclusion up front:

  • For an ordinary desktop EXE, you can push single-binary packaging quite far
  • However, being able to ship one EXE and not depending on the target Windows are different things
  • For shell extensions, Windows services, drivers, WebView2, and parts of WinUI 3, the real issue is less the file count and more what you register with the OS and what you assume about it
  • The most important thing in practice is to decide separately whether you want a single binary, no installer, or fewer OS dependencies

Put another way, the lines on Windows fall like this.

  • Consolidating the distribution into one artifact: largely achievable
  • Bundling additional runtimes: largely achievable
  • Moving toward xcopy deployment: depends on the kind of app
  • Eliminating dependencies on the target Windows itself: impossible
Where the lines fall on WindowsA diagram showing that consolidating the distribution into one artifact and bundling additional runtimes are largely achievable, that xcopy deployment depends on the kind of app, and that eliminating dependencies on the target Windows is impossible.Consolidate into one artifactLargely achievableBundle additional runtimesMove toward xcopy deploymentDepends on the kind of appEliminate OS-side dependenciesImpossible

Figure 2: What is achievable, what depends on the app type, and what is not achievable.

1.1 Terms Used in This Article

First, a quick pass over the words that come up repeatedly below.

Term Expansion What it means in this article
UCRT Universal C Runtime The standard C library portion that was split out of the C runtime in Visual Studio 2015. On Windows 10 and later it ships as a component of the OS
VC++ redistributable package Visual C++ Redistributable An installer that puts the Visual C++ runtime DLLs other than the UCRT onto the target machine
framework-dependent Deployment that depends on the .NET in the target environment A deployment form that assumes the .NET runtime is already installed on the target machine
self-contained .NET runtime bundled A form in which the app carries a full copy of the .NET runtime
single-file Single-file publish The .NET publish option that consolidates the distribution into one EXE
Native AOT Native Ahead-Of-Time A deployment form that compiles IL to native code ahead of time at publish. It does not use a JIT at run time
app-local deployment Application-adjacent deployment Placing DLLs in the same folder as the EXE so that only that app uses them
xcopy deployment Copy-only deployment Deployment that uses neither an installer nor administrator rights: drop the folder in place and it runs
SCM Service Control Manager The OS mechanism that manages registration, start, and stop of Windows services
UAC User Account Control The Windows security mechanism that controls elevation to administrator rights
Shell extension Shell extension A COM component that is loaded into processes such as File Explorer and runs there
Evergreen Continuously updated mode The distribution mode that leaves the WebView2 Runtime to a single shared copy that updates itself
Fixed Version Pinned-version mode The distribution mode that bundles a specific version of the WebView2 Runtime with your app
arch architecture, CPU architecture x86 / x64 / Arm64

Knowledge map for this article

For a Windows app, collecting the deliverable into a single EXE is largely feasible in itself, and self-contained, single-file, and Native AOT in .NET and static linking (/MT) in C/C++ are available, but dependencies on the OS version, the architecture, system DLLs, and the security model remain, so the dependency on the target Windows cannot be reduced to zero. self-contained and framework-dependent are mutually exclusive choices, and Native AOT effectively behaves as single-file at the cost of no longer supporting features such as built-in COM. WebView2 requires choosing how the runtime is supplied, either Evergreen or Fixed Version, and for WinUI 3 whether PublishSingleFile is possible depends on whether the app is packaged (MSIX) or unpackaged. For Shell extensions and drivers the real subject is the design of registration and signing, so app-local distribution is often easier to maintain than forcing everything into one EXE.

Single-binary packaging for Windows apps and OS dependenciesDiagram showing that reducing the deliverable to a single file and removing the dependency on the OS are separate axes, the single-file options available in .NET and in C/C++, and how host integrations such as WebView2 and WinUI 3 constrain the distribution format itselfrequiresincompatible withincompatible withrequiresrequiresincompatible withrequiresusesusesincompatible withusesrequiresconfigured byconfigured byincompatible withusesrequiresnot recommended forrecommended forrequiresusesrequiresSingle-Binary PackagingTarget Windows Platform DependencySelf-Contained DeploymentFramework-Dependent Deployment (.NET)Native AOTBuilt-in COM InteropSingle-File Publish (.NET)Static CRT Linking (/MT)Dynamic CRT Linking (/MD)Visual C++ RedistributableUCRT (Universal C Runtime)MSIX PackagingWinUI 3 / Windows App SDKMicrosoft Edge WebView2WebView2 RuntimeEvergreen Distribution Mode (WebView2)Fixed Version Runtime (WebView2)Shell Extension (Explorer Extension)COM (Component Object Model)Driver SigningDriver PackageApp-Local Deployment

In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (22 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle

2. Think of “Single Binary” in Four Levels

Here are those levels as a diagram first. They get harder from the bottom up, and the top one is unreachable on Windows.

Level A: One distribution artifactLargely achievableLevel B: No pre-installed language runtimeLargely achievableLevel C: No installation or registrationDepends on the kind of appLevel D: No dependency on the target WindowsUnreachable on Windows

Figure 3: The four levels of a single binary. They get harder from the bottom up, and Level D is unreachable.

Which of these four levels someone means by “we want a single binary” completely changes the work involved.

2.1 Level A: One distribution artifact

The most superficial level is this.

  • You can email a single file
  • You only need to put one file on a USB stick
  • You only place app.exe at the destination

This is about the apparent distribution unit. Even if the app extracts files temporarily at startup, or depends on OS-side DLLs, this condition alone can still be satisfied.

2.2 Level B: No pre-installed language runtime required

Next is the state where the app runs without pre-installing the .NET runtime or the VC++ redistributable package on the target machine.

  • Static linking for C/C++
  • .NET self-contained
  • .NET single-file
  • .NET Native AOT

At this level, the feeling of “I can carry this around on its own” gets much stronger.

2.3 Level C: No installation or registration required

From here, things suddenly get hard.

A plain EXE may run just by being dropped in place. But these are a different story.

  • Shell extensions
  • Windows services
  • Custom URL schemes and file associations
  • Drivers
  • Components loaded into other processes, such as Explorer or Office

This territory is not satisfied by just placing a file. It requires registration on the OS side, or wiring into a host.

The territory where dropping files in place is not enoughA diagram showing that shell extensions, Windows services, file associations, drivers, and components loaded into other processes are not satisfied by placing files, and require registration on the OS side or wiring into a host.Shell extensions and servicesPlacing files is not enoughAssociations and driversThings loaded into other processesOS-side registration and host wiring are required

Figure 4: Level C gets hard all at once because it is the territory that requires registration with the OS.

2.4 Level D: No dependency on the target Windows

This is impossible on Windows.

A Windows app ultimately runs on top of Windows APIs, the loader, the security model, and the device stack. Single-binary packaging only covers the app’s own area of responsibility. You are not taking the OS itself along with you.

3. The Areas Where a Single EXE Is Quite Achievable

Even on Windows, some apps lend themselves relatively well to a single EXE.

  • Desktop tools launched standalone
  • Business apps where the EXE itself owns the UI and the processing
  • Tools for communication, file processing, log collection, monitoring, or equipment control
  • Things that do not require host integration with Explorer or Office
  • UIs that do not assume a web runtime

For this type, many things are easy to fold into the app itself.

  • Your own code
  • Resources
  • The manifest
  • Default settings
  • Template data
  • Some third-party libraries
  • The language runtime itself

Furthermore, even without fully embedding DLLs inside the EXE, app-local deployment, placing DLLs next to the EXE, is a perfectly viable mainstream option on Windows. In practice,

  • a single app.exe
  • or app.exe plus a few adjacent DLLs
  • with no installer, no administrator rights, distributable via xcopy

this shape is often easier to maintain than forcing everything into one EXE.

app-local deployment as the practical landing pointA diagram showing that even without fully embedding DLLs into the EXE, a shape that places DLLs next to the EXE and can be xcopy-deployed with no installer and no administrator rights is often easier to maintain than compressing everything into one EXE.app.exe plus a few adjacent DLLsNo installer, no administrator rightsDeployable with xcopyOften easier to maintain than a forced single EXE

Figure 5: You can often meet the goal with app-local deployment without insisting on one EXE.

4. The Windows Dependencies That Remain Even With One EXE

If you assume “one EXE means no dependency on the target Windows,” this is where you get hurt. In reality, dependencies remain even with a single EXE.

4.1 OS version dependency

Each Windows API has a minimum supported OS. There are also x64 / Arm64 differences. So even with a single EXE, you need to pin down from the start:

  • Does it run down to Windows 10?
  • Is Windows 11 assumed?
  • Does it also need to run on Windows Server?
  • Which of x86 / x64 / Arm64 are targeted?

4.2 System DLL dependency

Even when you think you have a single EXE, at runtime you are of course using OS-provided components.

  • kernel32.dll
  • user32.dll
  • advapi32.dll
  • The COM infrastructure
  • The service control infrastructure

These are Windows’ area of responsibility.

4.3 Security model dependency

  • UAC
  • File ACLs
  • The Service Control Manager
  • The registry
  • Driver signing policy

Things like these cannot be absorbed into the app alone.

Dependencies that remain even with a single EXEA diagram showing that even with a single EXE, dependencies remain on the minimum supported OS and architecture of the Windows APIs, on system DLLs such as kernel32.dll and the COM infrastructure, and on the security model including UAC, ACLs, and driver signing policy.A single-EXE appOS version and archSystem DLLs and the COM infrastructureThe security modelNone of these can be absorbed by the app

Figure 6: Consolidating into one artifact leaves the dependency on Windows’ area of responsibility untouched.

4.4 Host and runtime dependency

If the design is not a standalone EXE but something that rides on a host, dependencies multiply at once.

  • Using WebView2: the WebView2 Runtime is needed
  • Using WinUI 3 / Windows App SDK: the deployment mode needs sorting out
  • Building a shell extension: registration on the Explorer side is needed

In other words, your choice of UI or integration often becomes, directly, your distribution difficulty.

UI and integration choices become distribution difficultyA diagram showing that using WebView2 requires the WebView2 Runtime, using WinUI 3 requires sorting out the deployment mode, and building a shell extension requires registration with Explorer, so that the choice of host or runtime becomes the distribution difficulty itself.Use WebView2The Runtime is requiredUse WinUI 3The deployment mode must be sorted outBuild a shell extensionRegistration with Explorer is required

Figure 7: The further you move from a standalone EXE, the faster dependencies pile up.

5. Realistic Trade-Offs by Technology

5.1 Native C/C++

Native C/C++ sits on the high-freedom side of single-binary packaging. Static linking is an option, and a standalone EXE can be consolidated quite far.

That said, more important in practice than cramming everything into one file are:

  • What to do about the UCRT and the VC++ runtime
  • Whether to place third-party DLLs app-local
  • How tightly to narrow the target CPU / OS

Starting point: the difference between /MT and /MD

In MSVC, what actually decides “do we carry the runtime ourselves” is this compiler option.

Option What gets linked What the target machine needs
/MD The DLL import libraries ucrt.lib and vcruntime.lib. At run time it uses ucrtbase.dll and vcruntime<version>.dll The UCRT and the VC++ runtime
/MT libucrt.lib, libvcruntime.lib, and libcmt.lib, linked statically No additional runtime

The smallest way to try it is this single line in a Developer Command Prompt.

cl /std:c++17 /EHsc /O2 /MT app.cpp /Fe:app.exe

There are three things to watch here.

  • When the C runtime was split apart in Visual Studio 2015, the UCRT became a component of Windows, and it ships as part of the OS on Windows 10 and later. If you target anything older than that, you need to redistribute it with vcredist.
  • Building a DLL with /MT is not recommended. A statically linked CRT keeps its state inside that DLL alone, so memory allocation, the locale, and the reach of _set_se_translator diverge between the EXE and the DLL. Sloppily matching them up as “the EXE is /MT, so the bundled DLLs are /MT too” leads to crashes when an allocation and its free cross the boundary.
  • /clr and /MT cannot be combined. If C++/CLI is in the mix, you are on the /MD side.
Three cautions about static linkingA diagram showing that the UCRT ships with the OS on Windows 10 and later but needs vcredist redistribution on older Windows, that building a DLL with /MT confines the CRT state inside the DLL and causes crashes when allocation and free cross the boundary, and that /clr cannot be combined with /MT.Static linking with /MTOlder Windows needs vcredist/MT on a DLL is discouragedCannot be combined with /clrCrashes when allocation and free cross the boundary

Figure 8: /MT is powerful, but it needs care on three points: the UCRT assumption, DLL boundaries, and C++/CLI.

5.2 .NET

.NET offers single-file, self-contained, and Native AOT, so the apparent distribution unit can be made quite small.

But you need to keep the distinctions straight.

  • framework-dependent: depends on the .NET installed in the target environment
  • self-contained: carries the .NET runtime with it
  • single-file: consolidates the distribution into one artifact
  • Native AOT: further reduces startup-time dependencies, but comes with feature constraints

“Single-file” does not mean “fewer OS dependencies.” What it mainly reduces is how scattered the app’s distribution artifacts are.

What single-file reduces is the scatter of distribution artifactsA diagram showing that framework-dependent relies on the .NET in the target environment, self-contained carries the runtime, and single-file only consolidates the distribution into one artifact, so single-file does not by itself reduce OS dependencies.framework-dependentDepends on the .NET in the target environmentself-containedCarries the .NET runtimesingle-fileConsolidates into one artifactDoes not reduce OS dependencies

Figure 9: Each .NET deployment form reduces a different thing and leaves a different thing behind.

Starting point: the three dotnet publish patterns

To just run them and see the difference, these three lines are enough.

rem 1. framework-dependent + single-file: the target machine needs the .NET runtime
dotnet publish -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true

rem 2. self-contained + single-file: carries the .NET runtime
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true

rem 3. Native AOT: publish after writing PublishAot in the csproj
dotnet publish -c Release -r win-x64

Microsoft officially recommends writing PublishSingleFile and PublishAot in the csproj rather than on the command line. That enables compatibility analysis during the build, which cuts down on problems you would otherwise only notice after publishing.

<PropertyGroup>
  <PublishSingleFile>true</PublishSingleFile>
  <PublishAot>true</PublishAot>
</PropertyGroup>

Note that specifying RuntimeIdentifier makes SelfContained default to true. When you want framework-dependent, pass --self-contained false explicitly. Publishing Native AOT on Windows requires the Desktop development with C++ workload in Visual Studio.

Trade-off: “making it one” is not free

Writing only about what is possible here would trip people up, so here is the price alongside it.

Startup-time extraction with single-file

  • By default, only managed DLLs are bundled. They are loaded into memory at startup and are not extracted to a folder. The native binaries of the runtime itself stay as separate files.
  • If you want the native binaries in the single file too, use IncludeNativeLibrariesForSelfExtract; if you want everything extracted before execution, use IncludeAllContentForSelfExtract. Both switch the behavior to extract-then-start, and on Windows the extraction goes under %TEMP%\.net. You can change the location with DOTNET_BUNDLE_EXTRACT_BASE_DIR, but do not point it somewhere writable by users or services with different privileges.
  • Enabling EnableCompressionInSingleFile makes the EXE considerably smaller, but startup gets slower because of the in-memory decompression. Microsoft’s own wording is to measure both size and startup cost before using it. The impact varies widely by app.

API incompatibilities with single-file

Once the distribution is a single artifact, code that assumes file paths breaks quietly.

API Behavior under single-file
Assembly.Location Returns an empty string
Assembly.CodeBase PlatformNotSupportedException
Assembly.GetFile IOException
Module.Name Returns the string <Unknown>

To touch files next to the EXE, move to AppContext.BaseDirectory; when you need the path of the executable, move to Environment.ProcessPath. Third-party libraries sometimes use these internally, so once you go single-file you need to launch it at least once on a real machine and confirm it works.

Path-dependent code breaks quietlyA diagram showing that consolidating the distribution into one artifact changes the behavior of path-dependent APIs such as Assembly.Location returning an empty string, so files next to the EXE should use AppContext.BaseDirectory, the executable path should use Environment.ProcessPath, and the app should be launched at least once on a real machine.Go single-filePath-dependent API behavior changesFiles next to the EXE: AppContext.BaseDirectoryThe executable path: Environment.ProcessPathLaunch once on a real machine to confirm

Figure 10: Once you go single-file, swap out how you obtain paths and confirm it on a real machine.

Feature constraints of Native AOT

In exchange for “fast startup, no runtime required,” the following become unavailable.

  • Dynamic loading such as Assembly.LoadFile
  • Runtime code generation such as System.Reflection.Emit
  • C++/CLI
  • Built-in COM on Windows
  • Trimming is mandatory, so the trimming constraints come along as well
  • It is effectively single-file, so the API incompatibilities above come along too
  • System.Linq.Expressions always runs interpreted, which is slower than runtime-generated code

The target platforms are fixed in advance as well. On .NET 8, Windows means x64 and Arm64; x86 was added in .NET 9. The idea of “one AnyCPU artifact” does not hold from the outset.

The price of Native AOTA diagram showing that Native AOT reduces startup-time dependencies but gives up dynamic loading, runtime code generation, C++/CLI, and built-in COM on Windows, carries the trimming and single-file constraints as well, and fixes the target platform in advance.Native AOTReduces startup-time dependenciesNo dynamic loading or runtime generationNo C++/CLI, no built-in COMTrimming and single-file constraints apply tooTarget OS and arch are fixed in advance

Figure 11: Look at the benefits of Native AOT together with the features you lose.

5.3 WebView2

Adopting WebView2 completely changes the single-binary difficulty. The real subject here is not the number of EXEs but how you handle the WebView2 Runtime.

Before “can we make it one EXE,” there are questions to answer first.

  • Do you assume the Runtime already exists in the environment?
  • Do you use Evergreen?
  • Do you bundle a Fixed Version?
  • How much responsibility do you take for offline distribution?

Roughly, the difference between Evergreen and Fixed Version is this.

  Evergreen Fixed Version
Who updates it Microsoft. It updates automatically You. You swap it in along with your app updates
What sits on the machine A single copy shared by all apps Bundled per app
Distribution size Barely grows Over 250 MB
How it gets installed The Bootstrapper is about 2 MB and downloads what it needs. For offline, bundle the Standalone Installer Ship the extracted set of binaries together with your app
Existing constraints You have to check whether it is on the machine before launch It cannot be run from a network path or a UNC path

The Evergreen Runtime comes preinstalled as part of the OS on Windows 11. Some Windows 10 machines still do not have it, though, so Microsoft itself words it as “even if you choose Evergreen, you should still distribute the Runtime.” In other words, checking from the app whether it is present and installing it if not is a step you need either way. Fixed Version gives you control over when updates happen, at the cost of adding several hundred MB to what you ship. This has to be decided up front, separately from the single-EXE question.

Decide the WebView2 distribution method firstA diagram showing that Evergreen is a single shared copy updated automatically by Microsoft but may be absent from a machine, that Fixed Version puts updates under your control at the cost of several hundred MB, and that either way the app needs a step that checks for the Runtime and installs it if missing.Adopt WebView2Evergreen: one shared copy, auto-updatedFixed Version: bundle and update it yourselfCheck for it and install if missingAdds several hundred MB to the distribution

Figure 12: The real subject with WebView2 is not the number of EXEs but how the Runtime is handled.

5.4 WinUI 3 / Windows App SDK

WinUI 3 also changes your distribution requirements the moment you adopt it. Choosing the UI technology is, in itself, choosing the distribution method.

Concretely, there are two axes to decide.

  • packaging: packaged (MSIX), packaged with external location, or unpackaged
  • runtime: framework-dependent or self-contained

And from a single-binary point of view, this combination constraint is what bites.

  • PublishSingleFile is available only for WinUI 3 apps that are unpackaged and self-contained, and it requires Windows App SDK 1.5 or later.
  • PublishSingleFile cannot be used for packaged apps, nor for packaged apps with external location.
  • Going unpackaged means losing package identity, so Windows features that depend on package identity, such as notifications, background tasks, file associations, and context menu extensions, become unavailable.

In other words, with WinUI 3, “make it one EXE” and “use the Windows extension features” collide head-on. If a single binary is the top priority, it is often faster to revisit the UI technology assumption first.

One EXE collides with package identityA diagram showing that PublishSingleFile in WinUI 3 works only for an unpackaged and self-contained configuration, that going unpackaged loses package identity, and that notifications and file associations then become unavailable, so a single EXE collides head-on with the Windows extension features.Want to use PublishSingleFileOnly unpackaged plus self-containedLoses package identityNo notifications, no associationsOne EXE collides head-on with the extension features

Figure 13: In WinUI 3, choosing one EXE means giving up package identity.

6. Areas That Inherently Require Registration and Dependencies

6.1 Shell extensions

A shell extension loaded into Explorer is a different animal from a “drop-in EXE.” Here the real subject is not the file count but how you register with Explorer.

6.2 Windows services

Even if the service executable itself can be a single file, distribution is a separate problem.

  • Registration with the SCM
  • Privileges
  • The logon account
  • Recovery settings

all need consideration. In other words, services are an area where you nail down “how to install” rather than “how to make it one EXE.”

6.3 Drivers

Drivers are even more clear-cut. They only come together as a package including the INF, signing, and the installation procedure, so they barely get onto the single-binary playing field in the first place.

The areas where registration and signing are the real subjectA diagram showing that shell extensions need registration with Explorer, Windows services need SCM registration plus privileges and a logon account, and drivers only come together with the INF file and signing, so the design of registration and dependencies matters more than the file count.Shell extensionsRegistration with ExplorerServicesSCM registration, privileges, accountDriversINF and signingRegistration design matters more than file count

Figure 14: In these three areas, nail down how to register rather than how to make it one EXE.

7. A Decision Table for Practice

For a rough judgment, this table is handy.

What you want to build Single-EXE feasibility What to think about first
Standalone Win32 / C++ tool High Static linking, target OS / arch
Standalone WinForms / WPF tool High Suitability of self-contained, single-file, Native AOT
WinUI 3 / Windows App SDK app Medium Deployment mode, additional dependencies
WebView2-based desktop UI Low to medium How the Runtime is distributed
Explorer context-menu extension or preview handler Low COM / registry registration
Windows service Medium SCM registration, privileges, update procedure
App that bundles a driver Low INF, signing, installation

The most important takeaway from this table is that “the number of binaries” and “the scope of distribution responsibility” are different things.

8. What to Decide First in Distribution Design

If you want single-binary packaging to succeed, there are things to decide before implementation.

8.1 Decide what you actually want to be “one”

  • Do you want one distribution artifact?
  • Do you want to eliminate runtime pre-installation?
  • Do you want to eliminate the installer?
  • Do you want to make offline updates easy?

The answer determines which technology you choose.

8.2 Pin down the minimum supported Windows and arch first

Both single-file and Native AOT are fundamentally OS / architecture specific. If you leave this vague and push ahead with “just make it one file,” you get stuck at the end with missing APIs or runtime mismatches.

8.3 Write down what you bundle and what you leave to Windows

In practice, just writing out this table means far fewer things go wrong.

  • What the app bundles
    • The main exe
    • Your own DLLs
    • Settings templates
    • The self-contained runtime
  • What you leave to Windows
    • System DLLs
    • OS APIs
    • SCM / registry / Explorer
    • The driver infrastructure
  • What you assume as separate prerequisites
    • WebView2 Runtime
    • VC++ Redistributable
    • Office / Excel
    • Dedicated drivers
Write down the three categories of responsibilityA diagram showing that simply writing down the three categories of what the app bundles, what is left to Windows, and what is assumed as a separate prerequisite means far less goes wrong when you distribute the app.Scope of distribution responsibilityWhat the app bundlesWhat is left to WindowsWhat is assumed separatelyWriting it down alone means less goes wrong

Figure 15: Write down the three categories of bundled, left to the OS, and assumed separately, before you start.

8.4 If you prioritize a single binary, reduce host integration

This one is quite effective.

  • Drop the shell extension and make it a plain EXE
  • Skip running as a service; use Task Scheduler or explicit launch instead
  • Use a native UI instead of WebView2
  • Keep COM closed within your own process

In short, the more you reduce designs that have the OS “load” or “register” your code, the closer you get to a single binary.

The less host integration, the closer you getA diagram showing that dropping the shell extension for a plain EXE, avoiding a service in favor of Task Scheduler or explicit launch, and using a native UI instead of WebView2 all reduce designs that ask the OS to load and register your code, which brings you closer to a single binary.Drop the shell extension for a plain EXEFewer designs that register with the OSNo service, use explicit launchNative UI instead of WebView2Closer to a single binary

Figure 16: If a single binary is the priority, reduce the host integration itself.

9. Summary

Single-binary packaging on Windows is feasible to a considerable extent. But it all comes down to this one statement:

You can make the app a single EXE. But you cannot make the Windows the app depends on into a single EXE.

Five points worth remembering in particular:

  • For an ordinary standalone EXE, you can push single-file distribution remarkably far
  • Static linking for C/C++, .NET single-file, and Native AOT are strong options
  • However, dependencies on the OS version, arch, system DLLs, and the security model do not disappear
  • For shell extensions, services, drivers, WebView2, and parts of WinUI 3, OS registration and additional runtimes become the main story
  • Whether single-binary succeeds is decided by separating, at the start, what exactly you want to be one

If you strongly prioritize a single binary, designing to lower the coupling with the OS at technology-selection time makes success far more likely.

The key to success is coupling with the OSA diagram showing that while the app can be made a single EXE, the Windows it depends on cannot, so if a single binary is a strong priority it is more likely to succeed when the design lowers coupling with the OS at technology-selection time.Make the app a single EXEAchievableMake the Windows it depends on a single EXENot achievableSo lower the coupling with the OS at selection time

Figure 17: Whether one-EXE packaging works is decided by how you design coupling with the OS at the start.

10. References

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Windows App Development

For Windows app distribution, you can avoid rework by designing single-file packaging, runtime bundling, the decision to adopt WebView2 or WinUI, and whether to run as a service, all together.

Frequently Asked Questions

Common questions about the topic of this article.

Can a Windows app be distributed as a single EXE file and nothing else?
For a desktop tool that is launched on its own, you can get quite far. Static linking in C/C++, .NET self-contained and single-file, and Native AOT all let you consolidate the distribution into one EXE. But being able to produce a single EXE and being independent of the target Windows are two different things: dependencies on the OS version, the CPU architecture, system DLLs, and the security model do not go away.
Does .NET single-file eliminate OS dependencies?
No. What single-file mainly reduces is how scattered the app's distribution artifacts are, not the OS dependencies. Framework-dependent deployment relies on the .NET installed in the target environment, self-contained carries the .NET runtime with it, and Native AOT reduces startup-time dependencies further but comes with feature constraints. Both single-file and Native AOT are fundamentally OS- and architecture-specific, so you have to pin down the minimum supported Windows and the arch at the very start.
Which kinds of apps are hard to ship as a single EXE?
Shell extensions, Windows services, drivers, WebView2-based UIs, and parts of WinUI 3. For these the real subject is registration with the OS and the handling of additional runtimes rather than the file count. A shell extension has to be registered with Explorer, a service needs registration with the SCM plus a design for privileges and the logon account, and a driver only comes together once the INF file and signing are included, so drop-in deployment simply does not work. With WebView2 you first have to decide how the WebView2 Runtime is distributed.
Is there a better distribution method than forcing everything into one EXE?
App-local deployment, placing DLLs next to the EXE, is a strong option. Even a shape like app.exe plus a few adjacent DLLs is often easier to maintain than compressing everything into one EXE, as long as it can be xcopy-deployed with no installer and no administrator rights. What matters is separating, at the start, whether you want one distribution artifact, no runtime pre-installation, or no installer at all.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog