How Per-Machine Windows Apps Silently Auto-Update — Three Patterns Learned from Chrome, Slack, and VS Code
In 2025, Slack retired the machine-wide MSI installer for Windows and consolidated on the per-user installer and MSIX — the Slack Help Center download page no longer lists the MSI, and the download links on slack.com follow suit. The .msi that IT departments had been pushing through SCCM and Intune for years is gone.
Per-user installs (the ones that land under %LocalAppData%) do not have a real auto-update problem. The write target and the executing account both live inside the same user’s scope. The moment you flip that to per-machine (the C:\Program Files variant), the difficulty jumps a full class. Standard users cannot write to Program Files, and in most enterprise environments today, employees do not have admin rights. That combination is the source of the pain.
The question of why Slack made that switch when it did, and how Chrome, Edge, and Docker Desktop all update silently, collapses into a single design problem: how to satisfy per-machine + silent auto-update + standard-user constraints simultaneously. This post walks through real implementations first, then abstracts them into three patterns.
Prerequisite: Per-Machine and Per-User Are Structurally Different
To get vocabulary straight, Windows desktop apps install in one of two ways.
| Aspect | Per-machine | Per-user |
|---|---|---|
| Install location | C:\Program Files / Program Files (x86) | %LocalAppData%\Programs etc. |
| Registry | HKLM\Software\... | HKCU\Software\... |
| Privilege required to install | Admin (UAC elevation) | Standard user |
| Privilege required to update | Admin (UAC by default) | Standard user |
| Users affected | All users on the machine | Only the installing user |
A per-machine install places one copy of the binaries on the box and shares them across every user. It is a natural fit for IT-managed distribution, but updates have to touch files under Program Files, which User Account Control (UAC) gates behind admin elevation. In a world where standard users have no admin rights, the naive “let the app overwrite itself” approach simply does not work.
Per-user installs sidestep this entirely. The user’s %LocalAppData% is fully writable by that user, so a silent, non-elevated replace is trivial. That is why Slack, Discord, Visual Studio Code User Setup, and the consumer Zoom client all pick per-user.
So “silent auto-update on a per-machine install” is really a request for an operation that a standard user is not supposed to be able to perform, done with no UAC prompt. The three patterns below are the canonical answers to that request.
Pattern A: Omaha-Style SYSTEM Service
The first pattern moves the update logic into a Windows Service running as SYSTEM. The app itself stays in the standard user’s context, but updates are handed off to a separate SYSTEM-privileged process. SYSTEM can write to Program Files, so no UAC prompt is ever shown.
Adopters
The archetype is Google Chrome with the Omaha (Chromium Updater) stack. After Chrome installs, Windows gains GoogleUpdate services and Scheduled Tasks that periodically check, download, and swap binaries in the background. Omaha is open source, living at chromium/src/chrome/updater.
The same shape shows up across the industry:
- Microsoft Edge uses an Omaha-fork-based
MicrosoftEdgeUpdateservice (see Microsoft Edge Update policies documentation) - Adobe Creative Cloud ships updates through resident services like Adobe Genuine Service and the Creative Cloud Desktop background service
- Dropbox runs
DropboxUpdateservices on a Chrome-like cadence - The enterprise Zoom MSI, with Automatic Update enabled, drives silent updates through the
ZoomAutoUpdaterservice - Docker Desktop more recently reached GA on silent component updates, using
com.docker.serviceto update without UAC even for standard users (see the Docker Desktop release notes)
The fact that Docker Desktop only reached silent update after years of iteration is itself evidence of how hard this pattern is to implement. Writing the Windows Service, designing the privilege boundary, and building recovery, differential apply, and signature verification into it is genuinely a product-sized effort.
The Skeleton
Three moving parts:
- The installer registers a Windows Service running as SYSTEM at install time
- A Scheduled Task periodically wakes the service, which then asks a version-check server whether a new build exists
- If a new version is available, the SYSTEM service swaps the binaries under
Program Files
For Chrome specifically, the Windows Task Scheduler wakes the updater once an hour and the updater itself throttles actual version checks to at most once every five hours by default, adjustable by enterprise policy (the underlying design is documented in the Chromium Updater design doc). Because the update path does not require Chrome to be running, users typically experience the new version as something that just appears the next time they launch.
Trade-offs
There are four upsides. Updates happen even when the app is closed. No UAC prompt disturbs the user. Rollback and recovery can be built into the SYSTEM side properly. Enterprise policy controls (stable/beta/dev channels, target version pinning, staged rollouts) can be exposed cleanly.
The downside is implementation cost. Building the Windows Service, signed installers, corruption recovery, multi-generation coexistence, and A/B rollout is not realistic for most product teams. Even though Chromium Updater is open source, adoption outside Chrome remains limited for exactly this reason.
Pattern B: MSIX (Platform Delegation)
The second pattern is to not write the update mechanism at all, and delegate to Windows. Ship the app as an MSIX package, declare the update source and cadence in an .appinstaller XML file, and Windows App Installer takes over the checking, downloading, and swapping. From the application’s side, updating is essentially “publish a new MSIX.”
Adopters
- Slack phased out MSI during 2025 and consolidated onto the per-user installer and MSIX (see the Slack Help Center)
- The new Microsoft Teams client is MSIX-based and rides on the Windows App SDK stack
- 1Password distributes an MSIX build through the Microsoft Store
- Docker Desktop also has a Microsoft Store MSIX distribution path
The way to read Slack’s decision is that they wanted per-machine deployment and silent updates together, but did not consider it worth carrying a Slack-specific SYSTEM service forever. Moving onto MSIX hands the entire update mechanism over to the OS.
The Skeleton
- Package the app as
.msix(or.appx) - Pin the
Publishervia EV code signing so Windows can enforce package identity strictly - Publish an
.appinstallerXML withHoursBetweenUpdateChecksand other update triggers - For enterprise, deploy the sideload certificate ahead of time via Intune or SCCM
A minimal .appinstaller looks like this:
<AppInstaller Uri="https://cdn.example.com/app.appinstaller"
Version="1.2.3.0"
xmlns="http://schemas.microsoft.com/appx/appinstaller/2018">
<MainPackage Name="Example.App"
Publisher="CN=Example, O=Example, C=JP"
Version="1.2.3.0"
Uri="https://cdn.example.com/app.msix" />
<UpdateSettings>
<OnLaunch HoursBetweenUpdateChecks="8" />
<AutomaticBackgroundTask />
</UpdateSettings>
</AppInstaller>Watch the Publisher CN: exact-match is a hard update prerequisite. Rotate a certificate or change a company name and Windows treats it as a different package and refuses to update.
On the Electron side, electron-windows-msix and the Electron Forge MSIX maker can build an MSIX from an existing Electron output, and Microsoft Learn’s MSIX packaging overview and Modernize your desktop apps for Windows cover the packaging path.
Trade-offs
The upside is that the update machinery costs almost nothing to write, both per-machine and per-user work, and Store distribution reuses the same package. On the enterprise side, MSIX slots naturally into Intune’s Windows App (Win32) and line-of-business paths.
There are three downsides. First, MSIX-specific constraints (Publisher CN match, sideload certificate distribution, the containerized runtime) clash with legacy Win32 assumptions (implicit admin, arbitrary path writes, low-level hooks). Second, native services, antivirus, and deep system integrations do not port cleanly. Third, fine-grained rollout control is less flexible than what Omaha-style stacks offer.
Pattern C: MDM Redistribution (No Auto-Update)
The third pattern is to skip auto-update in the app entirely, and have IT redistribute a new build through MDM (Microsoft Intune, SCCM, or an equivalent enterprise tool) every time. It is the most direct model in terms of governance, and it aligns well with how many enterprise IT teams already operate.
Adopters
- Notepad++ uses per-machine install with WinGUp, which polls for new versions every 15 days but ultimately hands off to an elevated installer run because the target lives in
Program Files - Visual Studio Code System Installer is documented as not auto-updating; VS Code recommends User Setup for personal use
- The 1Password MSI supports
MANAGED_UPDATE=1to disable auto-update and expects IT to push new versions via MDM - The legacy Slack MSI (retired in 2025) was the canonical example of this pattern
The Setting up Visual Studio Code on Windows documentation says of System Setup: “This setup requires administrator permissions and installs under Program Files. In-product updates also require elevation.” That last sentence is a deliberate acknowledgment that System Setup gives up on silent auto-update. The Notepad++ Upgrading page describes WinGUp checking for new versions every 15 days, but because the actual install writes into Program Files, applying the update still triggers an elevated installer run.
The Mechanism
- The installer is a per-machine MSI or EXE
- In-app auto-update is disabled (via MSI properties like
MANAGED_UPDATE=1, or policy registry keys) - When a new version ships, IT updates the MDM app catalog and redistributes to target endpoints
Trade-offs
The upside is low implementation cost and strong governance. Product teams get to say “IT owns update delivery; we just ship an installer.” IT gets full control over which version is live and when. For regulated industries and mixed Windows 10/11 environments that need to freeze on specific versions, this is a defensible choice.
The downside is responsiveness. Even for a zero-day patch, a monthly IT release cycle means multi-week delays. From the user’s side, “I cannot update this myself” has to be sold. In effect, this pattern trades an implementation cost for an ongoing operational cost that lives inside IT.
An Outlier: Steam’s ACL Loosening
Outside the three main patterns sits Steam. Steam is a per-machine install under C:\Program Files (x86)\Steam, and at install time it modifies the ACL on that folder to grant the Users group write-equivalent access. Standard users can then overwrite the app directory in place, and auto-update works.
The design is functional, but making Program Files writable means executables can be replaced by any standard user, which is widely regarded as a security anti-pattern because it broadens the surface for persistence and privilege escalation. Enterprise apps should not copy this approach. It is worth mentioning only as an example that “works” and “should be recommended” are not the same thing.
Choosing a Pattern
Laid out along five axes:
| Axis | A: Omaha-style service | B: MSIX delegation | C: MDM redistribution |
|---|---|---|---|
| Implementation cost | Very high | Medium | Low |
| Update responsiveness | High | Medium to high | Low |
| IT operational burden | Low | Low to medium | High |
| End-user experience | High (silent) | High (silent) | Medium (waits on IT) |
| Version governance | Medium (policy-driven) | Medium to high (Store, sideload) | High (IT holds it) |
The entry point for choosing usually comes down to four questions:
flowchart TD
Q1{Do end users have admin rights}
Q1 -- Yes --> P_ALL[A, B, or C all viable]
Q1 -- No --> Q2{Weekly or more frequent updates}
Q2 -- Yes --> Q3{Can you spend engineer-weeks on MSIX}
Q3 -- Yes --> B[Pattern B: MSIX]
Q3 -- No --> Q4{Can your team ship a Windows Service}
Q4 -- Yes --> A[Pattern A: Omaha-style]
Q4 -- No --> R[Consider per-user first]
Q2 -- No --> C[Pattern C: MDM redistribution]
The “consider per-user first” branch is intentional. Slack, Discord, VS Code User Setup, and the consumer Zoom client all pick per-user precisely to sidestep the per-machine + silent update problem. If corporate policy hard-requires per-machine, then and only then do the three patterns kick in. Otherwise, dropping requirements that can live in per-user usually beats fighting the platform.
Wrap-Up
Silent auto-update on a per-machine Windows install collides head-on with enterprise environments that do not hand out admin rights. Every real-world implementation that manages to satisfy both constraints falls into one of three patterns.
- Pattern A, the Omaha-style SYSTEM service, is the mainstream path for large infrastructure apps like Chrome and Edge, but implementation cost is enormous. Docker Desktop only reached silent updates after years of iteration, which is a data point on the difficulty.
- Pattern B, MSIX platform delegation, has been trending into the mainstream for enterprise Electron apps since 2025. Slack’s decision to retire the MSI and consolidate on MSIX in 2025 is emblematic of the shift.
- Pattern C, MDM redistribution, is still very much alive in governance-first enterprise environments. It is the model behind VS Code System Installer, Notepad++, and the retired Slack MSI, and it maps naturally to Intune-based IT operations.
Slack retiring the MSI signals that “per-machine, no in-house SYSTEM service, but do not give up on silent auto-update” is converging on MSIX at the industry level. Pattern C is not obsolete, though. The practical decision going forward is which of A, B, C, and “just go per-user” your app’s update cadence and your target IT environment’s governance posture put you into.
That’s all from mapping the per-machine silent auto-update problem across Chrome, Slack, and VS Code into three patterns, from the Gemba.
References
- Chromium Updater documentation
- Microsoft Edge Update policies documentation
- Zoom Automatic Update policies
- Docker Desktop release notes
- 1Password: Deploy 1Password with MDM
- Slack Help Center: Download Slack for Windows
- Notepad++ User Manual: Upgrading
- Visual Studio Code: Setting up on Windows
- Squirrel.Windows: Machine-Wide Installs
- Microsoft Learn: Modernize your desktop apps for Windows
- electron-windows-msix (GitHub)