Obfuscating a ClickOnce-Deployed App: Why Order of Operations Decides Whether It Even Launches
ClickOnce manifests store a hash of every file. Obfuscate after you generate them and the app refuses to launch with a validation error. Here is the exact build order that makes obfuscation and ClickOnce coexist.
There is a particular way a ClickOnce app fails after you add obfuscation that sends people down entirely the wrong rabbit hole: the app installs fine, then refuses to launch with a file validation or hash-mismatch error, and nothing in your code changed. The instinct is to suspect the obfuscator broke the assembly. It did not. ClickOnce is doing exactly what it is designed to do — refusing to run a file whose bytes do not match the hash it recorded — and the real bug is the order in which your build steps ran. Get the order right and obfuscation and ClickOnce coexist with no drama.
Why ClickOnce is unusually sensitive
Most deployment mechanisms copy files and run them. ClickOnce does more: its application manifest records, for every file in the deployment, a cryptographic hash and a size, and the ClickOnce runtime verifies each file against that record before launching. This is a tamper- and corruption-detection feature — it guarantees the user runs exactly what you published.
That guarantee is precisely what obfuscation collides with. Obfuscation rewrites the bytes of your assemblies — renaming, control-flow flattening, string encryption all change the file. If the manifest’s hash for MyApp.dll was computed over the pre-obfuscation DLL, the post-obfuscation DLL you actually ship no longer matches, and ClickOnce halts with a validation error before a line of your code runs.
The correct build order
The rule is one sentence: every transformation of the assemblies must happen before the manifests are generated, and manifest generation must be last. Concretely:
- Compile the project.
- Obfuscate the output assemblies.
- Re-apply the strong name if you delay-signed (obfuscation that re-emits the assembly must preserve or re-establish the strong name — see re-signing obfuscated assemblies).
- Authenticode-sign the binaries, if you sign them.
- Generate the application and deployment manifests with
mage/mageUIor the MSBuild ClickOnce targets — this is where the hashes are computed, over the now-final files. - Sign the manifests.
If you use Visual Studio’s one-click Publish, the trap is that publish does step 5 for you right after build, before any post-build obfuscation hook has run. The fix is to take manifest generation out of the Publish step and run mage yourself after obfuscation (a small CI script), or run obfuscation as part of the build such that it completes before the ClickOnce targets execute.
A minimal CI sketch
# 1-2: build then obfuscate the output
dotnet build -c Release -o publish
nebula protect publish/MyApp.dll # (plus other assemblies)
# 3-4: re-sign as needed
sn -R publish/MyApp.dll keypair.snk # if delay-signed
signtool sign /f cert.pfx publish/MyApp.dll # Authenticode (optional)
# 5: generate manifests over the FINAL files (hashes computed now)
mage -New Application -Processor msil -ToFile publish/MyApp.exe.manifest \
-Name "MyApp" -Version 1.2.0.0 -FromDirectory publish
mage -New Deployment -Processor msil -Install true \
-ToFile MyApp.application -AppManifest publish/MyApp.exe.manifest \
-providerUrl "https://downloads.example.com/MyApp/"
# 6: sign the manifests
mage -Sign MyApp.application -CertFile cert.pfx
Because the hashes in step 5 are taken over the obfuscated, signed assemblies, the ClickOnce runtime’s verification passes and the app launches.
Doing this with Nebula
Nebula slots into the obfuscation step above, and because it runs as a build/CI stage (see obfuscating on the build server only and the Azure DevOps / GitHub Actions guides), it is straightforward to place it before ClickOnce manifest generation. It preserves strong-name signing so the re-sign step is clean, and its keep-rules handle any WinForms/WPF reflection your app does. The only ClickOnce-specific rule is the ordering — get that right and nothing else changes.
Start with the free edition, or see the plans.
Try Nebula.NET
Harden your .NET code in minutes — start with the free edition.