You type winget on a fresh Windows Server 2025 box and PowerShell answers that the term is not recognized. The same happens under an account created a minute ago, and that is how most people meet winget, the Windows Package Manager client, and it rarely means the tool is missing. App Installer is already on the disk; it just has not been registered for that user yet. Once it is, a handful of commands installs, upgrades and removes software the same way apt or dnf does on Linux.
We cover the Server 2025 install path first and then the commands you will use every day, from search and install to a full upgrade of everything on the box. After that we move an app list from the server to a Windows 11 machine with winget export and import, and decode the error codes we actually hit. I ran through all of it in October 2026 on Windows Server 2025 and Windows 11 24H2 VMs with winget v1.29.380, and every output below comes from those two boxes, slow downloads and failed installers included.
winget commands at a glance
If you only came for the syntax, this table holds the commands we use most. Every one of them was run on the lab machines, and the steps after the table show what each one prints. All commands here are written for PowerShell, where a trailing # starts a comment; in cmd.exe, drop the release-link comment from the version line before you paste it.
| Task | Command |
|---|---|
| Check the version | winget --version |
| Search for a package | winget search 7zip |
| Show package details | winget show --id 7zip.7zip |
| Install a package | winget install --id 7zip.7zip -e |
| Install a specific version | winget install --id Git.Git -e --version 2.54.0 #https://github.com/git-for-windows/git/releases |
| List available upgrades | winget upgrade |
| Upgrade everything | winget upgrade --all |
| Hold a package back | winget pin add --id Microsoft.PowerShell --blocking |
| List installed packages | winget list |
| Uninstall a package | winget uninstall --id Notepad++.Notepad++ -e |
| Export the app list | winget export -o C:\lab\winget-apps.json |
| Import the app list | winget import -i C:\lab\winget-apps.json |
| Decode an exit code | winget error 0x8A150014 |
Notice that almost every command uses --id with -e (exact). A bare name such as winget install firefox matches dozens of packages and stops with an error, while the full package ID matches exactly one, so we write IDs everywhere, including in scripts. If you do not know an ID yet, winget search prints it in the second column, and you only have to look it up once.
Step 1: Check whether winget is installed
Start with the version flag, because it answers two questions at once: whether the winget alias exists for your account, and which client you have. Open PowerShell or Windows Terminal as the user who will run winget, not as SYSTEM, so you test the same profile your installs will use.
winget --version
A working install prints the client version and nothing else. If yours shows v1.28 or older, the steps below still work, but winget export will not keep custom install arguments, which arrived in 1.29.
v1.29.380
On both of our VMs the first attempt did not look like that. We created a local admin account called kibet and ran everything through it, and because that account had never signed in to the desktop, PowerShell returned this on Server 2025 and on Windows 11 alike:
The term 'winget' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Microsoft documents the reason: winget only becomes available after a user signs in for the first time, when the Store registers App Installer for that profile in the background. Until then Get-AppxPackage Microsoft.DesktopAppInstaller returns nothing for the user, even though the package is provisioned on the machine. You do not need to download anything. Ask Windows to register the package for the current user:
Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
Registration prints nothing when it succeeds, so do not wait for a message. It took 95 seconds on the Server 2025 VM and 13 seconds on Windows 11, and you can confirm it by asking for the package under your own account.
Get-AppxPackage Microsoft.DesktopAppInstaller | Select Name,Version
Our server reported App Installer 1.29.380.0 (the Windows 11 VM got 1.29.379.0, and both ran winget v1.29.380). Open a new PowerShell window afterwards so the alias is picked up.
Name Version
Microsoft.DesktopAppInstaller 1.29.380.0
If Get-AppxPackage -AllUsers Microsoft.DesktopAppInstaller (run it elevated, since -AllUsers needs admin rights) returns nothing either, App Installer really is missing from the machine and Step 2 applies. That happens on Windows Server 2022 and older, in Windows Sandbox, and on images where someone stripped the inbox apps.
Step 2: Install winget on Windows Server 2025
So does Windows Server 2025 ship winget? Yes. Microsoft lists Windows Server 2025 next to Windows 10 and 11 as a supported platform, with App Installer delivered through Windows Update on the server instead of through the Store. Our Server 2025 Datacenter template (build 26100) already had it provisioned for all users, which you can see from an elevated session:
Get-AppxPackage -AllUsers Microsoft.DesktopAppInstaller | Select Name,Version
Our provisioned copy was one release behind the current client. That is fine, because the copy that ships with the OS is recent enough to register and run, so you do not need to chase the msixbundle from GitHub on a supported server.
Name Version
Microsoft.DesktopAppInstaller 1.28.220.0
That 1.28.220.0 copy did not stay old for long. When the kibet account registered the package in Step 1, it came up as 1.29.380.0, so on a patched Server 2025 the per-user registration is the whole install. Here is where winget stands on each Windows Server release:
- Server 2025 with Desktop Experience: supported, App Installer included
- Server 2022 and 2019: not supported by Microsoft, runs experimentally
- Server Core (any release): winget does not run, per the winget-cli README
When App Installer is missing or broken, Microsoft’s own bootstrap is the Microsoft.WinGet.Client PowerShell module and its Repair-WinGetPackageManager cmdlet. It downloads App Installer and its dependencies (the VCLibs runtimes and Windows App Runtime 1.8 in the current release) and installs them, which replaces the old routine of fetching three .appx files by hand. Run it from an elevated Windows PowerShell window:
$progressPreference = 'silentlyContinue'
Install-PackageProvider -Name NuGet -Force | Out-Null
Install-Module -Name Microsoft.WinGet.Client -Force -Repository PSGallery | Out-Null
Repair-WinGetPackageManager -AllUsers
On the server the NuGet provider took 7 seconds and the module 44 seconds. On the Windows 11 VM the same two lines took 149 and 570 seconds, because the PowerShell Gallery download crawled, so give it time before you assume it hung. Confirm the module version and what it sees:
Get-Module Microsoft.WinGet.Client -ListAvailable | Select Name,Version
Get-WinGetVersion
Both report 1.29.380, matching the CLI, so you know the module and the client agree before you script against them.
Name Version
Microsoft.WinGet.Client 1.29.380
v1.29.380
On our server Repair-WinGetPackageManager -AllUsers and the -Latest variant both finished in under 10 seconds and changed nothing, because the client was already current. That is the expected result on a healthy box. We did not test the module on Server 2022, so treat that path as unsupported territory: it is the route we would try first there, but Microsoft gives no guarantee for it.
With winget working, winget --info is worth one look, because it prints every path winget uses (logs, settings, portable packages) and the admin settings that policy can flip:
winget --info
Note the Logs line for later. When an installer fails, winget writes a per-package log into that folder, and you will need it in the errors section.
Windows Package Manager v1.29.380
© 2026 Microsoft. All rights reserved.
Windows: Windows.Server v10.0.26100.32230
System Architecture: X64
Package: Microsoft.DesktopAppInstaller v1.29.380.0
WinGet Directories
Logs %LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir
User Settings %LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json
Portable Links Directory (User) %LOCALAPPDATA%\Microsoft\WinGet\Links
Portable Links Directory (Machine) C:\Program Files\WinGet\Links
Portable Package Root (User) %LOCALAPPDATA%\Microsoft\WinGet\Packages
Portable Package Root C:\Program Files\WinGet\Packages
Portable Package Root (x86) C:\Program Files (x86)\WinGet\Packages
Installer Downloads %USERPROFILE%\Downloads
Configuration Modules %LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules
Links
Privacy Statement https://aka.ms/winget-privacy
License Agreement https://aka.ms/winget-license
Third Party Notices https://aka.ms/winget-3rdPartyNotice
Homepage https://aka.ms/winget
Windows Store Terms https://www.microsoft.com/en-us/storedocs/terms-of-sale
Admin Setting State
LocalManifestFiles Disabled
BypassCertificatePinningForMicrosoftStore Disabled
InstallerHashOverride Disabled
LocalArchiveMalwareScanOverride Disabled
ProxyCommandLineOptions Disabled
ConfigurationProcessorPath Disabled
DefaultProxy Disabled
Your User Settings path is listed too, but on our fresh profile the settings.json file did not exist yet, so winget was running on its defaults. That is normal and nothing to fix.
Step 3: Accept the source agreements and search
winget ships with two sources that matter: winget, the community repository Microsoft hosts on its CDN, and msstore, the Microsoft Store. The Store source refuses to answer until you accept its terms, and a script running with --disable-interactivity cannot click Accept. Our very first search shows what that looks like:
winget search 7zip --disable-interactivity
It sat for over four minutes, because the winget source was also downloading its index, and then cancelled with exit code 0x8A150046.
Failed in attempting to update the source: winget
The `msstore` source requires that you view the following agreements before using.
Terms of Transaction: https://aka.ms/microsoft-store-terms-of-transaction
The source requires the current machine's 2-letter geographic region to be sent to the backend service to function properly (ex. "US").
One or more of the source agreements were not agreed to. Operation cancelled. Please accept the source agreements or remove the corresponding sources.
Add --accept-source-agreements once and winget records the acceptance for that user. In scripts we pass it on every call anyway, because a new user profile starts without it. On both VMs the first search with the flag still came back empty while the winget source finished its first index download (the errors section covers that wait). Once it had, the search returned results:
winget search 7zip --accept-source-agreements
Read the Match column, because it explains why each row is there. 7-Zip matched on its moniker, and most of the rest only carry 7zip as a tag, which is why ID beats name:
Name Id Version Match Source
7-Zip 7zip.7zip 26.05 Moniker: 7zip winget
Advanced Archive Password Recovery Elcomsoft.ArchivePassword 4.66.266.6965 Tag: 7zip winget
NanaZip M2Team.NanaZip 7.0.1882.0 Tag: 7zip winget
NanaZip Preview M2Team.NanaZip.Preview 7.0.1800.0 Tag: 7zip winget
Cram Nexalit.Cram 1.3.2 Tag: 7zip winget
7-Zip ZS mcmilk.7zip-zstd 26.02-v1.5.7-R2 Tag: 7zip winget
7-Zip ZS Non Darkmode mcmilk.7zip-zstd-ndm 26.02-v1.5.7-R2 Tag: 7zip winget
Dark7zip ozone10.7zip.Dark 26.02-0.70.0 Tag: 7zip winget
7zr 7zip.7zr 26.05 winget
QNapi QNapi.QNapi 0.2.3 Tag: p7zip winget
Before installing anything you do not know, read its manifest. winget show prints the publisher, the licence, the exact installer URL and its SHA256, which tells you what will actually run on the machine:
winget show --id 7zip.7zip
For 7-Zip the installer is the upstream MSI on GitHub, and winget checks the download against that hash before running it. We trimmed the Installer Url line from the block below; it points at the 7z2605-x64.msi asset on the 7-Zip GitHub releases page.
Found 7-Zip [7zip.7zip]
Version: 26.05
Publisher: Igor Pavlov
Publisher Url: https://7-zip.org/
Publisher Support Url: https://7-zip.org/support.html
Author: Igor Pavlov
Moniker: 7zip
Description: A file archiver with a high compression ratio.
Homepage: https://7-zip.org/download.html
License: LGPL-2.1
License Url: https://7-zip.org/license.txt
Copyright: Copyright © 1999-2026 Igor Pavlov.
Release Notes:
- Improved code for unpacking Z archives and Mach-O files.
- Some bugs and vulnerabilities were fixed.
Release Notes Url: https://github.com/ip7z/7zip/releases/tag/26.05
Documentation:
FAQ: https://7-zip.org/faq.html
Tags:
7zfiles
archive
archiver
compress
compression
decompress
decompression
extract
extractor
rarfiles
targzfiles
unarchive
unarchiver
unzip
zip
Installer:
Installer Type: wix
...
Installer SHA256: 3fe4ab99fa15629c652944309a2b2bc6e41fc933fc6b63f5df1ebcf3aea68263
Release Date: 2026-10-21
Offline Distribution Supported: true
Check the Installer Type line as well, since it matters more than it looks. A wix or msi package installs quietly from any session, while an msix package can fail from a non-interactive one, which bit us in Step 4.
Step 4: Install apps with winget install
A winget install downloads the installer named in the manifest, verifies its hash and runs it with the silent switches the manifest defines. Install 7-Zip by its exact ID from the winget source:
winget install --id 7zip.7zip --exact --source winget --accept-package-agreements --accept-source-agreements
It finished in 25 seconds. Look for the hash line in your own output, because winget refuses to run an installer whose hash does not match the manifest, and that check is the main reason we trust it over a browser download. The trimmed line is the Downloading URL of the same MSI.
Found 7-Zip [7zip.7zip] Version 26.05
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
...
Successfully verified installer hash
Starting package install...
Successfully installed
On a shared server you usually want the app for every user, not only for the account that ran the command. The flags we reach for most often are these:
--idwith-e: match one package exactly--scope machine: install for all users (needs an elevated shell)--silent: no installer UI at all--version: pin the install to one release--accept-package-agreements: skip the licence prompt in scripts
Put together, a machine-wide silent install of Notepad++ looks like this. Run it from an elevated shell, otherwise the machine scope asks for elevation and a script has nobody to answer.
winget install --id Notepad++.Notepad++ -e --scope machine --silent --accept-package-agreements --accept-source-agreements
That one took 98 seconds on the server and ended with Successfully installed. When you need an older release, for example to match what production runs, ask winget which versions it knows about first, since it can only install versions that still have a manifest in the source.
winget show --id Git.Git --versions
Versions come newest first, and we cut the list off after eleven entries.
Found Git [Git.Git]
Version
2.56.0.3
2.56.0.2
2.56.0
2.55.0.5
2.55.0.4
2.55.0.3
2.55.0.2
2.55.0
2.54.0
2.53.0.3
2.53.0.2
We deliberately installed Git 2.54.0, two releases behind, so the upgrade step would have something to do. Note that --version only sets the starting version and creates no pin, so the next winget upgrade --all moves the package to the newest release, which is exactly what happened to this Git in Step 5. To hold a server on the version your change ticket approved, add a blocking pin with winget pin add --id Git.Git --blocking after the install (Step 5 covers pins).
winget install --id Git.Git -e --version 2.54.0 --accept-package-agreements --accept-source-agreements #https://github.com/git-for-windows/git/releases
Git took 438 seconds, most of it the Git for Windows installer itself on a slow VM, so a long pause after Starting package install... is not a hang. The same run with Microsoft.PowerShell failed after 16 seconds with 0x80073d19, which we cover in the errors section, and installing it with --installer-type wix to force the MSI instead of the MSIX worked in 34 seconds.
Step 5: Upgrade apps with winget upgrade –all
Running winget upgrade with no arguments changes nothing. It only lists installed packages that have a newer version in a source, and winget list --upgrade-available prints the same table:
winget upgrade
Our old Git shows up, along with a Windows App Runtime update that winget itself depends on:
Name Id Version Available Source
Git Git.Git 2.54.0 2.56.0.3 winget
WindowsAppRuntime.1.8 Microsoft.WindowsAppRuntime.1.8 1.8.6 1.8.10 winget
2 upgrades available.
To upgrade all of it in one go, which is the winget answer to apt upgrade, add --all and the two agreement flags so nothing stops to ask:
winget upgrade --all --accept-package-agreements --accept-source-agreements
Both packages upgraded in 182 seconds. Read the whole output rather than only the success lines, because the last line tells you that something was skipped. The Git download URL is trimmed in the text below, and the screenshot after it shows the run untrimmed.
Name Id Version Available Source
Git Git.Git 2.54.0 2.56.0.3 winget
WindowsAppRuntime.1.8 Microsoft.WindowsAppRuntime.1.8 1.8.6 1.8.10 winget
2 upgrades available.
(1/2) Found Git [Git.Git] Version 2.56.0.3
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
...
Successfully verified installer hash
Starting package install...
Successfully installed
(2/2) Found Windows App Runtime 1.8 [Microsoft.WindowsAppRuntime.1.8] Version 1.8.10
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
Downloading https://aka.ms/windowsappsdk/1.8/1.8.260710003/windowsappruntimeinstall-x64.exe
Successfully verified installer hash
Starting package install...
Successfully installed
1 package(s) have version numbers that cannot be determined. Use --include-unknown to see all results.
Here is the untrimmed output of the same run from the Server 2025 VM, rendered from the transcript with download URLs included and a version check above it:

A closing message like that means one installed app has a version string winget cannot compare, so --all skipped it. Adding --include-unknown makes winget reinstall such packages from the source, which is usually harmless but not always what you want on a server, so we leave it off unless we know which package it is.
To keep one package out of the bulk upgrade, pin it. We installed PowerShell 7.6.7.0 while 7.6.9.0 was available and pinned it:
winget pin add --id Microsoft.PowerShell
winget pin list
Your pin list shows the default pin type, which is called Pinning, and that name matters in a moment.
Name Id Version Source Pin type
PowerShell 7-x64 Microsoft.PowerShell 7.6.7.0 winget Pinning
After that, winget upgrade --all skipped PowerShell and said 1 package(s) have pins that prevent upgrade. Then we ran winget upgrade --id Microsoft.PowerShell directly, and it upgraded to 7.6.9.0 in 34 seconds with the pin still in place. That is documented behaviour: a Pinning pin only guards --all. If you want a package frozen until you say otherwise, add the pin with --blocking, and pass a wildcard such as 7.6.* to --version for a gating pin that allows patch releases only. Remove a pin with winget pin remove --id Microsoft.PowerShell.
Step 6: List and uninstall packages
Running winget list shows everything installed on the machine, including apps that winget did not install. Filtering on the winget source keeps only what it can also update:
winget list --source winget
Two things stand out in the output. Some packages appear twice (the VCLibs and Windows App Runtime packages are each registered more than once), and the Windows App Runtime rows show > 1.8.10 instead of a plain version number, which is how winget printed the runtime we had just upgraded to 1.8.10:
Name Id Version
7-Zip 26.05 (x64 edition) 7zip.7zip 26.05.00.0
App Installer Microsoft.AppInstaller 1.29.380.0
Git Git.Git 2.56.0.3
Microsoft Edge Microsoft.Edge 156.0.4314.50
Microsoft Edge WebView2 Runtime Microsoft.EdgeWebView2Runtime 156.0.4314.50
Microsoft Visual C++ 2015 UWP Desktop Runtime Package Microsoft.VCLibs.Desktop.14 14.0.33728.0
Microsoft Visual C++ 2015 UWP Desktop Runtime Package Microsoft.VCLibs.Desktop.14 14.0.33728.0
Microsoft Visual C++ 2015 UWP Runtime Package Microsoft.VCLibs.14 14.0.33519.0
Microsoft Visual C++ 2015 UWP Runtime Package Microsoft.VCLibs.14 14.0.33519.0
Notepad++ (x64) Notepad++.Notepad++ 8.9.8.1
QEMU guest agent SoftwareFreedomConservancy.QEMUGuestAgent 110.0.2
WindowsAppRuntime.1.8 Microsoft.WindowsAppRuntime.1.8 > 1.8.10
WindowsAppRuntime.1.8 Microsoft.WindowsAppRuntime.1.8 > 1.8.10
Uninstalling uses the same ID you installed with. Add --silent, because a vendor uninstaller that opens a confirmation window can leave a remote session waiting on a prompt nobody sees, so we pass it in every script.
winget uninstall --id Notepad++.Notepad++ -e --silent
Notepad++ was gone in 2 seconds, against 98 seconds for the install.
Found Notepad++ (x64) [Notepad++.Notepad++]
Starting package uninstall...
Successfully uninstalled
A follow-up winget list --id Notepad++.Notepad++ returned No installed package found matching input criteria., which is the check we use in scripts to confirm a removal.
Step 7: Export and import an app list
Running winget export writes every installed package that it can match to a source into a JSON file, and winget import installs that list on another machine. We exported from the Server 2025 VM and imported on the Windows 11 VM:
winget export -o C:\lab\winget-apps.json --accept-source-agreements
Expect a few warnings here. Anything winget cannot match to a package in a source (Cloudbase-Init and the virtio drivers on our template) is left out of the file, while apps it can match are exported no matter who installed them.
Installed package is not available from any source: Microsoft Windows Application Compatibility Fix Database
Installed package is not available from any source: Virtio-win-driver-installer
Installed package is not available from any source: Cloudbase-Init 1.1.6
Installed package is not available from any source: Windows App Runtime DDLM 8000.921.1539.0-x6
Installed package is not available from any source: Windows App Runtime DDLM 8000.921.1539.0-x8
Installed package is not available from any source: Windows Package Manager Source (winget) V2
Installed package is not available from any source: WinAppRuntime.Main.1.8
Installed package is not available from any source: WinAppRuntime.Singleton
Your exported file is short. Without --include-versions, each entry is only a package ID, so the import installs whatever is newest on the target. We prefer that for workstations, because pinning versions in the file means you have to edit it every month or keep installing old builds.
{
"$schema" : "https://aka.ms/winget-packages.schema.2.0.json",
"CreationDate" : "2026-10-29T21:38:25.808-00:00",
"Sources" :
[
{
"Packages" :
[
{
"PackageIdentifier" : "Git.Git"
},
{
"PackageIdentifier" : "7zip.7zip"
},
{
"PackageIdentifier" : "SoftwareFreedomConservancy.QEMUGuestAgent"
},
{
"PackageIdentifier" : "Microsoft.Edge"
},
{
"PackageIdentifier" : "Microsoft.EdgeWebView2Runtime"
},
{
"PackageIdentifier" : "Microsoft.AppInstaller"
},
{
"PackageIdentifier" : "Microsoft.VCLibs.Desktop.14"
},
{
"PackageIdentifier" : "Microsoft.VCLibs.14"
},
{
"PackageIdentifier" : "Microsoft.WindowsAppRuntime.1.8"
}
],
"SourceDetails" :
{
"Argument" : "https://cdn.winget.microsoft.com/cache",
"Identifier" : "Microsoft.Winget.Source_8wekyb3d8bbwe",
"Name" : "winget",
"Type" : "Microsoft.PreIndexed.Package"
}
}
],
"WinGetVersion" : "1.29.380"
}
We imported this file exactly as exported, and that was a mistake worth learning from. The export is a snapshot of the whole machine, so it carries packages that only make sense on the source box. Before you copy the file anywhere, open it and delete these entries:
- Hypervisor tools such as the QEMU guest agent or VMware Tools
Microsoft.AppInstaller, which is winget itself- Runtime packages (VCLibs, Windows App Runtime) that winget pulls in as dependencies
Then copy the file to the target machine and run the import from an elevated shell there. You need the same two agreement flags, because the import runs every install back to back without stopping to ask.
winget import -i C:\lab\winget-apps.json --accept-package-agreements --accept-source-agreements
Our first run took 612 seconds and exited with 0x8A150034. Git, 7-Zip and WebView2 installed, Edge and the runtimes were skipped as already present, and two packages failed. This is the end of that run:
(3/5) Found QEMU guest agent [SoftwareFreedomConservancy.QEMUGuestAgent] Version 110.0.2
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
Downloading https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-qemu-ga/qemu-ga-win-110.0.2-1.el10/qemu-ga-x86_64.msi
Successfully verified installer hash
Starting package install...
Installer failed with exit code: 1603
Installer log is available at: C:\Users\kibet\AppData\Local\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir\SoftwareFree...
(4/5) Found Microsoft Edge WebView2 Runtime [Microsoft.EdgeWebView2Runtime] Version 156.0.4314.50
...
Successfully installed
(5/5) Found App Installer [Microsoft.AppInstaller] Version 1.29.380
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
Successfully verified installer hash
Starting package install...
Installer failed with exit code: 0x80073d19 : An error occurred because a user was logged off.
One or more imported packages failed to install
Our QEMU guest agent MSI returned 1603, most likely because the agent was already installed and running on the Windows 11 VM, and App Installer failed with the same MSIX error PowerShell gave us in Step 4. Neither failure stops the rest of the list, and that is the useful property of import: a second run took 95 seconds, skipped every package already present with Package is already installed, and only retried the two failures. App Installer went through on that retry.
A package that no longer exists behaves differently. We added a fake Contoso.DoesNotExist ID to a copy of the file, and without --ignore-unavailable the import printed Package not found: Contoso.DoesNotExist and aborted after 4 seconds with 0x8A150067, before installing anything. With the flag it logged the same line and carried on:
winget import -i C:\lab\winget-apps.json --ignore-unavailable --accept-package-agreements --accept-source-agreements
For a team baseline we keep the pruned JSON in Git and run it with --ignore-unavailable, because package IDs do get retired and one dead entry should not block a new laptop. If you would rather manage a list like this with a different tool, our guide to installing Windows applications from the command line compares the options, and Chocolatey is still the one we see most on older fleets.
Step 8: Script winget with PowerShell
Parsing winget’s text tables in a script is fragile, because the column widths change with the data. The Microsoft.WinGet.Client module from Step 2 returns real objects instead. This finds every package with an update waiting, which is what a patch report needs:
Get-WinGetPackage | Where-Object IsUpdateAvailable | Select Name,Id,InstalledVersion,AvailableVersions
With PowerShell 7.6.7.0 installed and 7.6.9.0 available, it returned one object. You can export that to CSV for a patch report without any text parsing.
Name Id InstalledVersion AvailableVersions
PowerShell Microsoft.PowerShell 7.6.7.0 {7.6.9.0, 7.6.8.0, 7.6.7.0, 7.6.6.0...}
Searching works the same way, and -MatchOption Equals is the module’s version of --exact:
Find-WinGetPackage -Id 7zip.7zip -MatchOption Equals
Same 26.05 package the CLI found, now as an object you can filter. That matters because the CLI pads its tables to the terminal width, so a script that splits on spaces breaks the first time a package name contains one.
Name Id Version Source
7-Zip 7zip.7zip 26.05 winget
Where should a script like this run? Not as SYSTEM, at least not without testing first. Microsoft states that the winget CLI is not supported in the system context, and our VMs agreed. Under NT AUTHORITY\SYSTEM the winget alias did not exist, and calling winget.exe by its full path inside C:\Program Files\WindowsApps exited with 0xC0000135 (a missing DLL). The docs suggest the module instead, but in Windows PowerShell 5.1 as SYSTEM every cmdlet we tried answered This cmdlet is not supported in Windows PowerShell. We did not test PowerShell 7 as SYSTEM. So for RMM agents and scheduled tasks, we run winget jobs under a dedicated local admin account, which is exactly how this whole lab was driven. If the script refuses to start at all, the execution policy is the usual culprit, and our fix for running scripts is disabled on this system covers it.
What broke: winget errors and their fixes
winget exits with a hex code on almost every failure, and the code tells you more than the message. These are the ones we hit on the two lab machines, with the meaning from Microsoft’s winget return codes list:
| Exit code | Meaning | Where we hit it |
|---|---|---|
| 0x8A150014 | No packages found | Typo in an ID, or the winget source failed to load |
| 0x8A150016 | Multiple packages found | winget install firefox |
| 0x8A15002B | No applicable update found | Upgrade or reinstall of a current package |
| 0x8A150034 | Failed to install one or more imported packages | winget import with a failing installer |
| 0x8A150046 | Source agreements were not agreed to | First run with --disable-interactivity |
| 0x8A150067 | One or more queries did not return exactly one match | Import file with a missing package |
| 0x80073D19 | A user was logged off (MSIX deployment) | MSIX installers from a non-interactive session |
You do not have to keep this table around, because winget decodes its own codes:
winget error 0x8A150014
You get the internal symbol and a one-line description, which is usually enough to search the winget-cli issue tracker.
0x8a150014 : APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND
No packages found
The sections below take the failures in the order you are likely to meet them on a new machine.
“Failed when searching source; results will not be included: winget”
We lost the most time to this one. On both fresh profiles, every search for several minutes returned only Store results (or nothing, with 0x8A150014), because the winget source index had not finished downloading. A winget source update took 392 seconds on Windows 11 and the search still failed straight after it. What finally worked on both machines was installing the WinGet.Client module and running Repair-WinGetPackageManager -AllUsers -Latest, after which the next search returned results. We cannot prove whether the repair or simply more time fixed it, so on a new machine run winget source update, wait, and use the repair cmdlet if the error persists. If the source still fails, winget source reset --force from an elevated shell puts the default sources back.
0x8A150016: Multiple packages found matching input criteria
A name search that hits more than one package stops and prints all of them. winget install firefox --disable-interactivity listed 103 Mozilla packages, one per locale plus the MSIX build, starting with these:
Multiple packages found matching input criteria. Please refine the input.
Name Id Source
Mozilla Firefox (en-US) Mozilla.Firefox winget
Mozilla Firefox (MSIX) Mozilla.Firefox.MSIX winget
Mozilla Firefox (ach) Mozilla.Firefox.ach winget
Mozilla Firefox (af) Mozilla.Firefox.af winget
Mozilla Firefox (an) Mozilla.Firefox.an winget
Pick the ID you want and install it with --id Mozilla.Firefox -e. The list changes as publishers add packages, so a bare name that works today can start failing next month, which is why you should never ship one in automation.
0x8A15002B: No available upgrade found
No real failure here, but your scripts will treat any non-zero exit code as one. Running winget install --id 7zip.7zip -e on a machine that already had the current 7-Zip printed Found an existing package already installed. Trying to upgrade the installed package... and then exited with 0x8A15002B, the same code an up-to-date winget upgrade returns. In a provisioning script, count 0x8A15002B (decimal -1978335189) as success.
0x80073d19: An error occurred because a user was logged off
Both MSIX installs we attempted from a scheduled task (PowerShell 7 and App Installer) failed with this code. MSIX deployment from a scheduled task with no signed-in session proved unreliable in our lab rather than impossible, because the same App Installer package went through on a retry from the same task. The fix depends on the package. PowerShell publishes an MSI as well, so --installer-type wix installed it in 34 seconds, and App Installer succeeded on a simple retry. If a package only ships an MSIX, run that install from a normal signed-in session.
Installer failed with exit code: 1603
Code 1603 comes from Windows Installer, not winget, and means the MSI itself failed. Ours was most likely the QEMU guest agent trying to install over a running copy. winget prints the path to a per-package log in the DiagOutputDir folder that winget --info lists, and that log names the real cause.
Setting up a fresh server from scratch? Our Windows Server 2025 installation guide covers the steps before this one. Install Windows Terminal early too, because the wide winget tables wrap badly in the old console host.