Winget for patch management: automating third-party updates with Microsoft's package manager
There is a package manager sitting on every current Windows machine you manage, and the odds are you have never scripted it. Winget ships with Windows 10 and 11 as part of the App Installer, it can enumerate and upgrade a large slice of the third-party software on a device, and it costs nothing. That makes winget patch management the obvious starting point for anyone tired of Chrome, Zoom and 7-Zip sitting three versions behind, whether the machines belong to clients or to your own company. It is also not a complete answer, and pretending otherwise is how estates end up half-patched with nobody noticing. This guide covers how winget sources packages, the commands that matter at scale, pinning and exclusions, and the honest limits. By the end you should be able to run a winget update pass on a test machine and know exactly when winget alone is not enough.
Where winget actually gets its packages
Winget ships with two sources, which you can see with winget source list. The msstore source is the Microsoft Store. The one that matters for patching is winget, the community repository, which is a public GitHub project (microsoft/winget-pkgs) containing tens of thousands of manifests.
The important word is manifests. The repository does not host installers. Each manifest is a small YAML file pointing at the publisher's own download URL, with a SHA256 hash of the expected binary and the silent-install switches. When you run an upgrade, winget downloads the installer from the vendor's CDN, verifies the hash, and runs it with the recorded switches. Microsoft validates submissions and the hash check stops tampering in transit, but the freshness of any given package depends on someone, often the vendor, often a volunteer, submitting the new version. Popular packages are updated within hours of release. Obscure ones can lag by weeks. That variance is worth knowing before you promise anyone a patch window.
Winget patch management in practice: listing and upgrading at scale
Start on a test machine, in an elevated prompt, with three commands:
winget list enumerates everything in Add/Remove Programs and attempts to match each entry to a package in the repository. Entries with a value in the Available column have a newer version waiting.
winget upgrade shows only the outdated software, which is the report most RMM patch modules charge extra for.
winget upgrade --all --silent --accept-package-agreements --accept-source-agreements upgrades everything it can, unattended. The two accept flags matter: without them, winget will sit waiting for a keypress that never comes when you run it from a script.
Two refinements for real estates. First, --include-unknown catches applications that did not write a version string to the registry, so winget cannot tell whether they are current; without the flag they are silently skipped. Second, you can target a single application with its exact ID, for example winget upgrade --id Google.Chrome --silent, which is how you turn a CVE announcement into a one-line remediation. Find IDs with winget search chrome, and always use the ID rather than the display name, because names collide and IDs do not.
Test-machine pass: run winget upgrade and screenshot the list, then winget upgrade --all --silent --accept-package-agreements --accept-source-agreements --include-unknown, then winget upgrade again. Whatever is still listed is your exception pile, and the exception pile is where the actual work lives.
Pinning and exclusions: keeping winget away from the fragile stuff
Every estate has software that must not move: the line-of-business app validated against one specific runtime, the driver utility that breaks on anything newer. Winget's answer is pins.
winget pin add --id Mozilla.Firefox excludes the package from upgrade --all. You can still upgrade it deliberately by ID.
winget pin add --id Notepad++.Notepad++ --version "8.6.*" is a gating pin: winget will apply point releases within 8.6 but refuse to cross to 8.7. This is the closest winget gets to a rollout ring.
Adding --blocking hardens a pin so that even an explicit upgrade by ID is refused unless someone passes --force. Use it for anything where an unplanned upgrade means a support incident.
winget pin list and winget pin remove do what you would expect.
One caveat: pins are stored per machine and per context. They are not policy. If you need the same exclusions on 400 devices, you are scripting pin creation into your deployment, not setting it once centrally.
The SYSTEM problem, and why RMM scripts trip on it
Winget is delivered as an MSIX package registered per user, which produces the classic failure: your RMM runs scripts as LocalSystem, LocalSystem has no winget on its PATH, and the job dies with a command-not-found error. The workaround is to resolve the executable directly, for example in PowerShell: $winget = (Resolve-Path "$env:ProgramFiles\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe\winget.exe")[-1].Path, then invoke & $winget upgrade --all .... Recent winget builds support running under SYSTEM for machine-scope packages, but it remains the single most common reason a winget script that works in your interactive session fails from the RMM.
The deeper version of the same problem is per-user installs. Software installed into a user's profile, which is how Zoom and plenty of Electron apps arrive, is invisible to a SYSTEM-context winget. You can patch every machine-scope package on the device and still leave a vulnerable per-user Chrome sitting in someone's AppData. Any pass that only runs as SYSTEM is a partial pass. It is worth saying twice. It is a partial pass.
What winget alone will never give you
Run winget honestly for a month and the gaps sort into two piles.
Coverage gaps. Line-of-business software, niche vertical apps and anything the vendor distributes behind a login will not be in the repository. Some mainstream apps are present but self-update anyway, so winget and the vendor's own updater end up co-managing the same binary. And the unknown-version entries from earlier never fully disappear.
Orchestration gaps. Winget has no fleet view, no rollout rings, no maintenance windows, no retry logic and no reporting. It cannot tell you that 38 of 400 devices failed because the app was open, and it cannot prove to an auditor that the estate was patched within 14 days, which is precisely the evidence Cyber Essentials expects your tooling to produce. Winget is an engine. The scheduling, targeting, exception handling and reporting around it are the actual product, which is the same conclusion we reached when looking at third-party patch management software more broadly.
Rule of thumb: winget answers "can this device update this app silently?" It does not answer "is my estate patched?" The first is a command. The second is a system, and you still have to build or buy the system.
Failure modes: winget in a scheduled task versus winget under management
| Failure | Winget in a scheduled task | Winget driven by an RMM |
|---|---|---|
| App open during upgrade | Silent failure, nobody notices | Failed job surfaces, retried in the next window |
| Per-user install missed | Invisible indefinitely | Detected by inventory, flagged as an exception |
| Package lags in the repository | Machine reports nothing wrong | Version drift visible against the fleet |
| Audit asks for patch evidence | You grep event logs and hope | Report exists already |
Where this fits with Helios
Helios uses winget as one of the engines behind its third-party patching, but wraps it in the parts winget lacks: fleet-wide inventory, maintenance windows, per-client exclusions, retry on failure and reporting you can hand to an auditor. When an upgrade fails, Helio, the platform's AI agent, investigates the exit code and either fixes the cause or raises a ticket with the diagnosis attached rather than a bare error. The honest caveat stands: no platform puts absent packages into the repository, so your line-of-business exceptions remain your problem, just a visible one.
Helios is an AI-native RMM and PSA in one product, with flat monthly pricing and every feature on every plan. 14-day trial, no feature gating, no card required. Start free.