CleanUpdate.Core
0.1.3
dotnet add package CleanUpdate.Core --version 0.1.3
NuGet\Install-Package CleanUpdate.Core -Version 0.1.3
<PackageReference Include="CleanUpdate.Core" Version="0.1.3" />
<PackageVersion Include="CleanUpdate.Core" Version="0.1.3" />
<PackageReference Include="CleanUpdate.Core" />
paket add CleanUpdate.Core --version 0.1.3
#r "nuget: CleanUpdate.Core, 0.1.3"
#:package CleanUpdate.Core@0.1.3
#addin nuget:?package=CleanUpdate.Core&version=0.1.3
#tool nuget:?package=CleanUpdate.Core&version=0.1.3
<div align="center">
β¬οΈ CleanUpdate
A small, modern, secure self-update library for .NET desktop apps.
</div>
What is it?
Desktop apps need to update themselves β but you can't overwrite a running .exe, XML-feed updaters
feel a decade old, and rolling your own means re-solving hashing, signatures, and safe file swaps.
CleanUpdate is an instance-based, async self-update library (a cleaner alternative to
AutoUpdater.NET). A headless core checks a source, downloads and verifies a package, and applies it;
an optional WPF or WinForms dialog gives you the whole experience in one call. It reads a JSON
feed or GitHub Releases, verifies every package (SHA-256, optionally RSA-signed), and applies zip
updates through an atomic swap helper that backs up, replaces, and rolls back on failure.
Reach for it when you ship a portable .NET desktop app and want secure, reliable in-app updates without standing up an update server.
- π§© UI is optional.
CleanUpdate.Coreis fully headless; the WPF/WinForms dialogs are one-call wrappers. - π Secure by default. Every package needs a SHA-256 hash; missing hashes are rejected. Add a public key to require RSA signatures.
- π Atomic zip swaps. An external helper replaces the app folder after exit, with automatic rollback.
- π Bring your own source. A hosted JSON feed or GitHub Releases β both via one
IReleaseProviderseam.
How it works
flowchart LR
A[App starts] --> B{CheckAsync}
B -->|up to date| Z[Do nothing]
B -->|update available| C[Show prompt / your UI]
C --> D[DownloadAndVerifyAsync]
D -->|SHA-256 + signature OK| E{Package type}
E -->|installer| F[Launch installer]
E -->|zip| G[Stage files β launch swap helper]
G --> H[App exits]
H --> I[Helper: backup β swap β rollback on error]
I --> J[Relaunch app]
Quick start
- Install the package for your UI (or the headless core).
- Add a few lines to your app to check and apply updates (see Minimal examples).
- Publish a release β the
cleanupdateCLI turns yourdotnet publishfolder into the zip, hashes,feed.jsonand signatures for you. - Upload those artifacts and point the app's
Feedat yourfeed.json.
Install
dotnet add package CleanUpdate.Wpf # WPF app with the ready-made dialog
dotnet add package CleanUpdate.WinForms # WinForms app with the ready-made dialog
dotnet add package CleanUpdate.Core # headless / custom UI (pulled in by the UI packages)
All packages target net8.0 and .NET Framework 4.8 (UI packages use net8.0-windows). The
swap helper (CleanUpdate.Tool.exe) is bundled in Core and copied into your output automatically β no
manual step for the zip flow.
Minimal examples
WPF
using CleanUpdate;
using CleanUpdate.Wpf;
using var updater = new Updater(new UpdaterOptions
{
Feed = new Uri("https://example.com/update.json"),
ExitApplication = () => Application.Current.Shutdown() // let files be swapped cleanly
});
// Check the feed and, if an update is available (and not skipped/postponed), show the dialog.
await WpfUpdater.CheckAndPromptAsync(updater, owner: this);
WinForms
using CleanUpdate;
using CleanUpdate.WinForms;
using var updater = new Updater(new UpdaterOptions
{
Feed = new Uri("https://example.com/update.json"),
ExitApplication = () => Application.Exit()
});
await WinFormsUpdater.CheckAndPromptAsync(updater, owner: this);
Headless / custom UI
using CleanUpdate;
using CleanUpdate.Download;
using var updater = new Updater(new UpdaterOptions { Feed = new Uri(feedUrl) });
UpdateCheckResult check = await updater.CheckAsync(ct);
if (check.ShouldPrompt) // available and not skipped/postponed
{
var progress = new Progress<DownloadProgress>(p =>
Console.Write($"\rDownloading⦠{p.Fraction * 100:0}%"));
string file = await updater.DownloadAndVerifyAsync(check.Info!, progress, ct);
await updater.ApplyAndRestartAsync(check.Info!, file, ct);
}
For production, also set
CurrentVersion, aChannel, and β strongly recommended β sign your releases and setPublicKeyXml. See the Security model.
Feed format
The feed is one release object, or an object with a releases array (multiple versions/channels). The
updater picks the highest version on the requested channel.
Minimal:
{
"version": "2.1.0",
"package": {
"type": "zip",
"url": "https://downloads.example.com/myapp/myapp-2.1.0.zip",
"sha256": "<lowercase hex sha-256 of the file>"
}
}
Full:
{
"releases": [
{
"version": "2.1.0",
"channel": "stable",
"mandatory": { "enabled": false, "minVersion": "2.0.0" },
"changelog": "β’ New dashboard\nβ’ Faster startup\nβ’ Bug fixes",
"package": {
"type": "zip",
"url": "https://downloads.example.com/myapp/myapp-2.1.0.zip",
"size": 10485760,
"sha256": "<lowercase hex sha-256 of the file>",
"signature": "<base64 RSA-SHA256 signature, required when a public key is set>"
}
}
]
}
The complete field reference is in docs/api.md.
Publishing updates
You don't hand-write the feed or compute hashes. The cleanupdate .NET tool turns a publish
folder into everything the app consumes β and signs it with the same algorithm the app verifies with:
dotnet tool install --global CleanUpdate.Cli
dotnet publish -c Release -o publish
cleanupdate pack --input publish --version 2.1.0 --name myapp \
--base-url https://downloads.example.com/myapp \
--key private.xml --delta --feed feed.json
That emits the release zip + .sha256, a feed.json (release upserted by version), the
package/feed RSA signatures, and a delta manifest.json. Generate a key pair once with
cleanupdate keygen. Full workflow, options, and CI usage: docs/publishing.md.
Security model
CleanUpdate verifies every update package before it is applied.
By default:
- every package needs a SHA-256 hash β a missing hash is rejected, not installed unverified;
- serve the feed and packages over HTTPS;
- set a public key and RSA signatures become required β unsigned or tampered packages are refused.
What each layer proves:
- SHA-256 β the package bytes were not corrupted or swapped (but not who produced them).
- RSA package signature β the package was signed with your private key.
- Feed signature β the whole feed document (version, url, installer
args, β¦). Without it, unsigned metadata is only as trustworthy as its transport.
Full setup β generating keys, signing packages, signing the feed, and zip-extraction hardening β is in docs/security.md.
Delivery modes
Chosen per release via package.type:
zipβ atomic swap (recommended for portable apps). Extracted to a staging folder, verified, and handed toCleanUpdate.Tool.exe, which relocates itself, waits for your app to exit, backs up the current install, swaps in the new files, rolls back on any error, and relaunches.installerβ run a setup. The verified.exe/.msiis launched withpackage.args(optionally elevated viaRunAsAdmin). No helper required.
Advanced
- GitHub Releases as a source β point at a repo's Releases, nothing to host.
- Delta updates β ship a manifest so only changed files download.
- Skip / Remind me later β attach a
StateStore(JsonFileStateStoreby default) and choices persist across runs;CheckAsyncthen reportsSuppressed/ShouldPrompt. Mandatory updates are never suppressed. The ready-made dialogs wire this automatically. - Changelogs β
await updater.GetChangelogAsync(info, ct)returns the inlinechangelogor fetcheschangelogUrl. The dialogs display it for you. - Custom sources β implement
IReleaseProviderto read from a CDN, an internal service, or a file share.
API reference
The full type, options, and method reference lives in docs/api.md. Most apps only touch a handful:
| Member | Purpose |
|---|---|
Updater.CheckAsync(ct) |
Fetch releases and decide if a newer one applies. |
Updater.DownloadAndVerifyAsync(info, progress, ct) |
Resumable download + SHA-256/signature verification. |
Updater.ApplyAndRestartAsync(info, file, ct) |
Apply (installer or zip-swap) and restart. |
Updater.UpdateAsync(progress, ct) |
Convenience: check β download β verify β apply. Honors Skip/Later. |
Contributing & releasing
Building, testing, packing, the repository layout, and the CI/release workflows are documented in docs/releasing.md.
License
MIT Β© CleanUpdate contributors
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Framework | net48 is compatible. net481 was computed. |
-
.NETFramework 4.8
- System.Text.Json (>= 8.0.5)
-
net8.0
- No dependencies.
NuGet packages (2)
Showing the top 2 NuGet packages that depend on CleanUpdate.Core:
| Package | Downloads |
|---|---|
|
CleanUpdate.WinForms
Ready-made, dark-themed WinForms update dialog for CleanUpdate. Depends on CleanUpdate.Core. |
|
|
CleanUpdate.Wpf
Ready-made, themable WPF update dialog for CleanUpdate. Depends on CleanUpdate.Core. |
GitHub repositories
This package is not used by any popular GitHub repositories.