Updates, Versioning & Telemetry

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

Shipping version 1.0 of a desktop application is the start of its life, not the end. After release the same few questions come back every week. Which build is this user running? How does version 1.1 reach them without a reinstall? What happens when 1.1 turns out to be broken? How do you find out it is broken before the support inbox fills up? On Windows each question has a different answer for each distribution channel, and the answers interact: a version number that is perfectly good for WinGet can be rejected by the Microsoft Store, and a crash report channel that exists for Store installs says nothing about a ClickOnce install. This page walks through the four jobs in order — numbering, updating, rolling back and observing — for Bookshelf Desktop, the running scenario of this section.

The page was written against these versions, current in October 2026:

  • .NET 10 LTS, latest patch 10.0.13, with C# 14. .NET 11 is at release candidate 2 (general availability is expected 10 to 13 November 2026); nothing on this page depends on a .NET 11 change. .NET 8 and .NET 9 both end support on 10 November 2026, so a "which runtime is the user on" telemetry field has a real use.

  • Visual Studio 2026 18.11.1 and the Windows App SDK 2.5.1 (stable, released 16 September 2026), which adds the windows.diagnosticServiceModule package extension discussed under crash reporting.

  • WinGet: winget-cli 1.29.380 and winget-create 1.12.13.0.

  • Microsoft Store Policies 7.20, published 15 September 2026.

  • CommunityToolkit.Mvvm 8.4.2 for view models. Versioning and telemetry code in this page sits in services behind interfaces, so the view models stay free of it.

How the packages themselves are built, signed and listed belongs to the publishing pages of this section: .NET Publishing Options, MSIX Packaging & Signing and Distribution Channels. This page assumes you know which channel you ship through and covers what happens to a build after it leaves your machine. CI/CD with GitHub Actions shows how to stamp the version numbers below from a pipeline.

The Update Lifecycle at a Glance

Every channel answers the same four questions, but with different machinery. The diagram shows where each channel gets its notion of "newer", who performs the update, and what a rollback means.

flowchart TD A["New build with a higher version"] --> B{"Which channel?"} B -- "Microsoft Store" --> S["Store certifies and signs, then offers the package; Windows updates it in the background"] B -- "App Installer file" --> I["You replace the .appinstaller and MSIX on your host; Windows checks per UpdateSettings"] B -- "WinGet" --> W["Manifest merged into winget-pkgs; user runs winget upgrade"] B -- "ClickOnce" --> C["Deployment manifest points to the new application manifest; checked before each start"] B -- "MSI or EXE installer" --> M["You own it: in-app updater, a third-party library, or a manual download"] S --> R1["Rollback: publish a higher version; older package only for new acquisitions"] I --> R2["Rollback: ForceUpdateFromAnyVersion lets a lower version install"] W --> R3["Rollback: new manifest version, or user pins and installs an older version"] C --> R4["Rollback: minimumRequiredVersion and republish"] M --> R5["Rollback: your updater decides"]

The common thread is that every automatic mechanism compares a version number, and moves only to a higher one unless you explicitly say otherwise. Getting numbering right is therefore the foundation of everything else.

Version Numbers in a .NET Desktop App

A single Bookshelf Desktop build carries several version numbers, each read by a different consumer. They can all differ, which is a feature when you understand it and a source of confusion when you do not.

Number Where it lives Read by Format

Assembly version

AssemblyVersion attribute, set by the AssemblyVersion property

The CLR, as part of the assembly identity

Four parts, major.minor.build.revision

File version

AssemblyFileVersion attribute, set by the FileVersion property

Windows Explorer’s Details tab, installers, some update tools

Four parts

Informational version

AssemblyInformationalVersion attribute, set by the InformationalVersion property

People: shown as "Product version" in file properties; not used at run time

Any string, typically SemVer plus a commit hash

Package version

Version attribute of Package/Identity in Package.appxmanifest

Windows packaging, the Store, App Installer

Four parts, major.minor.build.revision

ClickOnce publish version

ApplicationVersion / publish profile

ClickOnce update checks

Four parts

NuGet package version

Version, VersionPrefix, VersionSuffix

NuGet restore

SemVer, with an optional pre-release suffix

The assembly version is part of an assembly’s identity: two assemblies that differ only by version number are, to the runtime, different assemblies. The informational version is documented as text for people, with no run-time role. That division suggests a practical rule for an application (as opposed to a library): keep AssemblyVersion stable or tied to the major version, and put the real release identity in the file, informational and package versions.

Stamping the Version in the Project File

The SDK-style project file is the single place to set all of them. A Directory.Build.props file in the solution root lets every project in the Bookshelf Desktop solution (Bookshelf.Core, Bookshelf.Desktop, the test projects) share the same numbers.

<!-- Directory.Build.props -->
<Project>
  <PropertyGroup>
    <!-- The SemVer release the humans talk about -->
    <VersionPrefix>1.4.0</VersionPrefix>
    <!-- Empty for a final release; "rc.1", "beta.2" etc. for a pre-release -->
    <VersionSuffix></VersionSuffix>

    <!-- Identity for the runtime: only changes with a breaking release -->
    <AssemblyVersion>1.0.0.0</AssemblyVersion>

    <!-- What Explorer shows on the Details tab; the build number comes from CI -->
    <FileVersion>1.4.0.$(BUILD_NUMBER)</FileVersion>

    <!-- Appends the commit hash (SourceRevisionId) when Source Link is present -->
    <IncludeSourceRevisionInInformationalVersion>true</IncludeSourceRevisionInInformationalVersion>
  </PropertyGroup>
</Project>

Two details are worth knowing. IncludeSourceRevisionInInformationalVersion defaults to true, and from .NET 8 the SDK fills SourceRevisionId with the commit hash when Source Link is present, so the informational version of a default build already ends in the commit hash. A bare $(BUILD_NUMBER) that is empty on a developer machine produces a malformed file version, so a real build gives it a default (for example 0) in the same file.

Showing the Version to the User and to Telemetry

Users report problems using the version they see in the About box, so that is the number that matters most for support. Read it from the informational version, which carries the commit hash, behind a small service so the view model stays testable.

using System.Reflection;

namespace Bookshelf.Desktop.Services;

public interface IAppInfo
{
    string Version { get; }          // "1.4.0+3f2a9c1"
    string ShortVersion { get; }     // "1.4.0"
}

public sealed class AppInfo : IAppInfo
{
    public AppInfo()
    {
        var assembly = typeof(AppInfo).Assembly;
        Version = assembly
            .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
            .InformationalVersion
            ?? assembly.GetName().Version?.ToString()
            ?? "0.0.0";

        // Drop the "+commit" build metadata for display
        var plus = Version.IndexOf('+');
        ShortVersion = plus >= 0 ? Version[..plus] : Version;
    }

    public string Version { get; }
    public string ShortVersion { get; }
}

A packaged app has a second source of truth, the package version. In a packaged WinUI 3 or WPF app with package identity, Windows.ApplicationModel.Package.Current.Id.Version returns it; an unpackaged app has no package, so the assembly attributes remain the portable choice. Log both when they exist, because a mismatch between the manifest and the assemblies is one of the commonest packaging mistakes.

Semantic Versioning for Applications

Semantic Versioning (SemVer 2.0.0) writes a version as MAJOR.MINOR.PATCH. Increase MAJOR for incompatible API changes, MINOR for backward-compatible features and PATCH for backward-compatible fixes; a hyphen introduces a pre-release identifier (1.0.0-alpha.1, ordered below 1.0.0) and a plus sign introduces build metadata (for example 1.0.0+exp.sha.5114f85), which is ignored when comparing precedence. A book on building CLI applications with .NET recommends semantic versioning together with a plan for rolling back in its distribution chapter, and the advice transfers to a desktop app with one adjustment: an application has no public API to break, so decide what "breaking" means to your users.

Part Library meaning Suggested meaning for Bookshelf Desktop

MAJOR

Incompatible API change

Incompatible change to the data on disk, a dropped runtime or OS, a changed licence

MINOR

New backward-compatible feature

A new feature or screen, a new import format

PATCH

Backward-compatible fix

A bug fix, a dependency security update

Pre-release

-beta.1, -rc.1

A build for a Store flight, a WinGet preview channel or an internal ring

Build metadata

+sha

The commit hash and CI run number, shown only in the About box

The wrinkle on Windows is that the formats do not match. SemVer has three numeric parts and optional text; the assembly, file, package and ClickOnce versions have exactly four numeric parts and no text. The conversion therefore has to be defined once, written down and automated.

SemVer Four-part version Rule

1.4.0

1.4.0.0

Append a zero revision. For the Store this is mandatory (see below).

1.4.1

1.4.1.0

The patch maps to the third part.

1.5.0-rc.1

1.4.900.0, shipped on a separate pre-release channel (a Store flight or a preview feed)

Four-part versions cannot carry text, and the fourth part must stay 0 for the Store. Number a pre-release just below the release it precedes (here the third part of the previous minor, raised to a high counter) and keep the -rc.1 label in the informational version.

Two rules keep this honest. Every four-part part is limited to a 16-bit integer (0 to 65535) in a Store package, so a CI run number or a date such as 20261023 in the build part would overflow; use a short counter. And a pre-release must never be compared as a higher version than the release it precedes, so a package for 1.5.0-rc.1 should use a lower four-part number than the one for 1.5.0.

MSIX Package Versions

The package version lives in the Identity element of Package.appxmanifest. It is the number App Installer and the Store compare, so it is the number that decides whether an update happens at all.

<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10">
  <Identity
    Name="Example.BookshelfDesktop"
    Publisher="CN=Example Publisher"
    Version="1.4.0.0"
    ProcessorArchitecture="x64" />
  <!-- ... Properties, Dependencies, Applications ... -->
</Package>

The schema defines Version as a required version string in quad notation, major.minor.build.revision, for example 1.2.3.4. The Microsoft Store adds rules on top of the schema, from its package requirements page:

Store rule Consequence for Bookshelf Desktop

For Windows 10 and 11 packages the last (fourth) part is reserved for Store use and must be 0 when you build

Build 1.4.0.0, never 1.4.0.17. Put the CI counter in the third part if you need one.

The other parts are integers from 0 to 65535, and the first part cannot be 0

The very first release is 1.0.0.0 (or higher), not 0.9.0.0.

The Store always offers the highest-versioned applicable package, and submissions may arrive in any order

A forgotten bump is not an error; the old package simply never wins. Higher number means newer.

Several packages may share a version if their architecture differs

An x64 and an Arm64 package can both be 1.4.0.0; two x64 packages cannot.

The package Publisher must match the subject of the signing certificate, and Name is case-sensitive. Those are packaging concerns and appear in MSIX Packaging & Signing; for versioning the one thing to remember is that the same identity (Name plus Publisher) with a higher Version is an update, while a different identity is a different application.

Updates per Channel

Microsoft Store

For an MSIX submitted to the Store, the Store re-signs the package with a Microsoft certificate after certification and hosts it on its content network, and the operating system performs the update; you write no update code. Your tasks are to submit a package whose Version is higher than the live one and to wait for certification. The Store offers every customer the highest-versioned package that applies to their device. Store package flights let you send a build to a chosen group before everyone, which is how a pre-release channel is done there. EXE and MSI submissions are different: you host the installer, sign it yourself, and the installer is not allowed to change after submission, so updates are a new submission each time.

App Installer: OnLaunch and AutomaticBackgroundTask

An .appinstaller file is an XML file that names the MSIX and says how to update it. Users install by opening the .appinstaller, which associates the app with it; later you update the file to point at the new package, and Windows picks the change up. The documented update settings are small:

Setting Minimum Windows 10 version Behaviour

OnLaunch

1709

Checks for an update when the user launches the app. Can show UI.

HoursBetweenUpdateChecks (attribute)

1803

0 to 255 hours, default 24: do not check again if the last check was more recent.

ShowPrompt (attribute)

1903

Whether the user sees UI for the update.

UpdateBlocksActivation (attribute)

1903

true: the user must take the update or close the app. false: the user can start without updating and the update is applied silently later. Needs ShowPrompt set to true.

AutomaticBackgroundTask

1803

Checks every 8 hours regardless of launches. Cannot show UI.

ForceUpdateFromAnyVersion

1903 (the schema reference page says 1809)

Allows moving to a lower version as well as a higher one. Without it the app can only move up.

The following file mirrors the documented structure: launch-time checks with a prompt, a background check and no forced downgrade.

<?xml version="1.0" encoding="utf-8"?>
<AppInstaller
  xmlns="http://schemas.microsoft.com/appx/appinstaller/2021"
  Version="1.4.0.0"
  Uri="https://downloads.example.com/bookshelf/Bookshelf.appinstaller">

  <MainPackage
    Name="Example.BookshelfDesktop"
    Publisher="CN=Example Publisher"
    Version="1.4.0.0"
    ProcessorArchitecture="x64"
    Uri="https://downloads.example.com/bookshelf/Bookshelf_1.4.0.0_x64.msix" />

  <UpdateSettings>
    <OnLaunch HoursBetweenUpdateChecks="12" ShowPrompt="true" UpdateBlocksActivation="false" />
    <AutomaticBackgroundTask />
  </UpdateSettings>
</AppInstaller>

A few cautions. The Version on the root element is the version of the .appinstaller file, and the MainPackage Name, Publisher and Version must match the package identity exactly. Updates can be served over https, http or smb. And the ms-appinstaller: URI protocol has been disabled by default since December 2023, so a "Install" button that uses it no longer works on most machines: link to the .appinstaller file itself, or publish to the Store. The file uses the 2021 schema namespace on purpose: Visual Studio still generates the 2017/2 schema, which does not support ShowPrompt, UpdateBlocksActivation or HoursBetweenUpdateChecks and silently ignores them, as Distribution Channels explains.

WinGet

WinGet updates are pull-based: nothing happens until the user, a script or a management tool runs winget upgrade. Your part is to publish a new manifest version to the winget-pkgs repository, which the wingetcreate tool automates.

# What can be upgraded? (no arguments lists available upgrades)
winget upgrade

# Upgrade one package by id
winget upgrade --id Example.BookshelfDesktop

# Upgrade everything that has an update
winget upgrade --all

# Include packages whose installed version cannot be determined
winget upgrade --all --include-unknown

# Include packages with a non-blocking pin
winget upgrade --all --include-pinned

Source: upgrade command

The documentation notes that packages without a version are always "latest", so they are skipped unless --include-unknown is given, and that pinned packages are skipped by --all unless --include-pinned is given (and then only those with non-blocking pins). winget update is an alias of winget upgrade.

On the publisher side, wingetcreate update takes the id of an existing manifest and the URL of the new installer, generates the new manifest version and, with --submit, opens the pull request. The same flow is scripted in CI/CD with GitHub Actions.

# After publishing the new installer to a stable URL
wingetcreate update Example.BookshelfDesktop `
  --version 1.4.0 `
  --urls https://downloads.example.com/bookshelf/Bookshelf-1.4.0-x64.exe `
  --submit

Source: winget-create

The update command takes the package id followed by --urls (-u), --version (-v) and --submit (-s); wingetcreate update --help lists the rest. The pull request goes through automated validation and, for many packages, a moderator review, so a WinGet release can lag behind your own site by hours or days. The manifest must also list the correct installer hash, and the installer URL must never be rewritten afterwards: a changed file at an old URL fails the hash check.

ClickOnce

ClickOnce applications are self-updating by design. The deployment manifest holds the version clients should run, and a client compares it with what is installed. For .NET (Core 3.1, 5 and later) the options are narrower than for .NET Framework:

Feature .NET Framework .NET 5 and later (WPF and WinForms)

Check before start

Default strategy

The only supported strategy

Check after start in the background, UpdateInterval and UpdateIntervalUnits

Supported

Unsupported

ApplicationDeployment API in System.Deployment.Application

Supported

Not available; since .NET 7 deployment properties are exposed through environment variables

Mandatory update with a minimum required version

minimumRequiredVersion on the deployment element

Same attribute in the deployment manifest

Manifest tool

Mage.exe

dotnet-mage

With the default before-start check, a client that finds a newer version either installs it (when it is required) or asks the user whether to upgrade or start the existing version. To make an update mandatory, set the minimum required version; the documentation says that only the before-start strategy guarantees an older version can no longer run.

<!-- Deployment manifest fragment: clients below 1.3.0.0 must update before running -->
<deployment install="true" minimumRequiredVersion="1.3.0.0">

ClickOnce is not available for WinUI 3 applications, and updates need network connectivity: with no connection the app runs without checking, whatever strategy you chose.

Installers You Host, and Third-Party Updaters

MSI and EXE installers (built with tools such as WiX, Inno Setup or NSIS) carry no update mechanism of their own that Windows drives for you. Options are a manual download, a managed deployment, WinGet, or an updater inside the application. Velopack is a third-party installer and update framework for .NET desktop apps; it is not a Microsoft product and is only named here so you can evaluate it at https://velopack.io/. Whatever you choose, an in-app updater must follow the rules in the security page: fetch over https, verify a signature on what you download, and never run unsigned code. See Security & App Isolation, which discusses update-channel security.

Rolling Back

A rollback is a decision made at release time, not a button you press during an incident, so plan it per channel.

  • Store. Create a new submission, remove the problematic package and upload the earlier one. People who already got the bad package keep it, because your older package has a lower version; this only stops new acquisitions. To fix the people who already have it, submit a package with a version higher than the bad one as soon as you can.

  • App Installer. Updates normally only move up. Add ForceUpdateFromAnyVersion set to true to allow a lower version, point the .appinstaller at the previous package, and keep the MainPackage identity identical.

  • WinGet. Publish a new, higher manifest version rather than editing an old one, so the fix reaches everyone who runs winget upgrade.

  • ClickOnce. ClickOnce documents that updates can be rolled back to an earlier version by an end user or an administrator, and publishing a version with a raised minimum required version forces everyone forward.

  • Everything. Keep the previous release’s installers, symbols and a note of the schema version of any data the app wrote. A rollback that reads a database written by a newer version is a data bug, not a packaging problem.

Because the answer depends on data compatibility, make migrations forward-only and tolerant: Bookshelf Desktop’s local database stores a schema_version, and an older build that meets a higher number opens the file read-only and says why instead of corrupting it.

Crash Reporting and Telemetry After App Center

Visual Studio App Center was the default crash and analytics service for many desktop apps. It was scheduled for retirement on 31 March 2025, after which sign-in and API calls stopped; the Analytics and Diagnostics features were given extensions, the latest of which keeps them running until the end of March 2027. Microsoft’s guidance is to move to other services and points Store app developers to Partner Center for analytics and diagnostics. There is no single drop-in replacement from Microsoft for a desktop app, so assemble one from the layers below.

Layer What it gives you Applies to

Windows Error Reporting (WER)

Crash and hang reports collected by the operating system when users opt in

Every app on Windows

Partner Center health report

Crash rate, hang rate, devices affected, stack traces and trends per app version

Store submissions

windows.diagnosticServiceModule (Windows App SDK 2.5.1)

Lets a self-contained .NET MSIX app declare diagnostic modules so WER can load them and collect actionable crash dumps

Self-contained .NET MSIX apps

Local dumps

Full or mini dumps written locally on crash, no upload

Any app, on machines you control

Your own handlers and logging

Exceptions you catch, with context you choose

Your code

OpenTelemetry

Logs, metrics and traces exported to a backend you choose

Your code, with your backend

Windows Error Reporting and the Partner Center Health Report

WER lets users send Microsoft information about application faults and unresponsive applications; users choose whether to report, and administrators can override with Group Policy. Developers can register with the Windows Desktop Application Program to receive information about problems customers hit. For apps in the Store, the same data reaches you through the Health report in Partner Center, which shows crash counts and unresponsiveness rates, stack traces, affected OS and device combinations, and trends, and lets you compare app versions. The crash rate is calculated from devices that opted in to share optional diagnostic data, so it is a sample, not a count of every user. To get symbolicated stacks, submit the .msixupload file that Visual Studio creates: it includes an .appxsym file (a compressed PDB of public symbols), and if you omit it no crash analytics are available for the app.

The Windows App SDK 2.5.1 release notes list "Windows Error Reporting support": the windows.diagnosticServiceModule package-manifest extension, which a self-contained .NET MSIX app can use to declare diagnostic modules so that WER can load them and collect actionable crash dumps. The release notes do not show the manifest XML, so this page does not either; read the current release notes before relying on it.

Local Dumps and Unhandled Exceptions

For machines you control (a pilot group, a test lab) WER can write dumps locally after a crash. The feature is not enabled by default and needs administrator rights. The settings live under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps: DumpFolder (default %LOCALAPPDATA%\CrashDumps), DumpCount (default 10) and DumpType (0 custom, 1 mini, 2 full; default 1). A subkey named after the executable, for example Bookshelf.Desktop.exe, overrides the global settings for that program only. Full dumps contain process memory, which can include book data, file paths and tokens, so keep them in a folder with a restricted ACL and delete them when the case closes. See Collecting user-mode dumps.

Inside the app, a WPF application has three places where an exception can surface after every try block has failed: Application.DispatcherUnhandledException for the UI thread, AppDomain.CurrentDomain.UnhandledException for any thread when the process is about to end, and TaskScheduler.UnobservedTaskException for tasks nobody awaited. Hook all three in App.xaml.cs, route them to one ILogger (see Dependency Injection & the Generic Host) together with the version from IAppInfo, and decide deliberately whether to continue. Setting Handled to true swallows an exception and leaves the application in an unknown state; for most exceptions it is safer to log, offer to restart and let the process end so that WER and the Health report record the crash. A WinUI 3 app uses Application.UnhandledException and WinForms uses Application.ThreadException.

Telemetry with OpenTelemetry

OpenTelemetry is a cross-platform, open standard for emitting logs, metrics and traces. In .NET it builds on the platform APIs you already have — ILogger<T> for logs, System.Diagnostics.Metrics.Meter for metrics and System.Diagnostics.ActivitySource for traces — and exports to a backend over the vendor-neutral OTLP protocol or a specific exporter. The backend is your choice: Azure Monitor and Application Insights, Prometheus and Grafana, or a commercial vendor. For a desktop app the model differs from a server in two ways: the telemetry leaves a machine you do not own, so it must be opt-in, and the network is unreliable, so exporters must buffer and drop rather than block the UI thread.

using System.Diagnostics.Metrics;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;

// In App.xaml.cs, after the user's consent has been read from settings
if (settings.TelemetryEnabled)
{
    builder.Services.AddOpenTelemetry()
        .ConfigureResource(r => r.AddService("Bookshelf.Desktop", serviceVersion: appInfo.ShortVersion))
        .WithMetrics(m => m
            .AddMeter(BookshelfMetrics.MeterName)
            .AddOtlpExporter());   // endpoint from OTEL_EXPORTER_OTLP_ENDPOINT
}

public static class BookshelfMetrics
{
    public const string MeterName = "Bookshelf.Desktop";
    private static readonly Meter Meter = new(MeterName);

    // Counts, never titles or file names
    public static readonly Counter<long> BooksImported = Meter.CreateCounter<long>("books.imported");
}

The packages involved are OpenTelemetry, OpenTelemetry.Exporter.OpenTelemetryProtocol and, for the hosting extension method used above, OpenTelemetry.Extensions.Hosting. The first two are listed on the Microsoft page; the hosting package and its fluent configuration API are documented by the OpenTelemetry .NET project, and the API can change between package versions.

Choose the metrics before the tool. A short list of counts answers most product questions: sessions started, features used, import failures by category, startup time, and the runtime and OS version (to know when you can drop .NET 8 or Windows 10). None of them needs a book title or a path.

Privacy Obligations

Crash reports and telemetry describe a person’s behaviour, so they carry legal and platform obligations. The page cannot give legal advice; it lists the engineering consequences.

  • Opt-in by default. Ask on first run, store the answer in the settings service, and make it changeable in the settings window. Register the OpenTelemetry providers only when the answer is yes.

  • Collect the minimum. Version, OS, runtime and anonymous counts. No book titles, library contents, file paths, user names, machine names or tokens. Scrub exception messages: an IOException message contains a path.

  • Document it. State in a privacy policy what you collect, why, where it is stored, for how long and how to opt out. The Microsoft Store Policies apply to apps in the Store; read the current version (7.20 at the time of writing) for the privacy requirements that affect you.

  • Respect the platform setting. WER and the Partner Center crash rate are driven by the user’s Windows diagnostic data choices; your own telemetry must not behave as if those choices did not exist.

  • Protect what you keep. Dumps and logs can hold personal data. Restrict access, set a retention period, and delete on request.

  • Test it. A unit test that serialises every telemetry event for a representative session and asserts that no book title, path or token appears is cheap and catches the regression that a new developer will eventually introduce.

Pitfalls

  • Building a Store package with a non-zero fourth part (1.4.0.17), or putting a date in a version part and overflowing 65535.

  • Rewriting a file at an installer URL that a WinGet manifest hashes, so every install fails the hash check.

  • Linking to ms-appinstaller: when the protocol is disabled by default on user machines.

  • Setting UpdateBlocksActivation to true without ShowPrompt set to true.

  • Expecting ClickOnce background update checks or the ApplicationDeployment API on .NET 5 and later.

  • Assuming a Store rollback removes the bad build from machines that already have it.

  • Shipping without symbols, leaving the Health report with unreadable stacks.

  • Enabling telemetry before consent, or sending titles and paths in events.

Summary

Give every build one source of truth for its version, convert SemVer to the four-part forms once and automate it, and remember that the Store wants the fourth part to be zero. Let each channel update the way it was designed to — the Store and App Installer in the background, WinGet on request, ClickOnce before start — and decide your rollback path before you ship, because each channel rolls back differently. For observation, combine Windows Error Reporting and the Partner Center health report with your own exception logging and, only with consent, minimal OpenTelemetry metrics, and treat every dump and event as personal data until proven otherwise.