Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
February 7, 2025

Winget Package Management with PowerShell

I've been sharing my thoughts and code for managing my work environment with PowerShell. It's not that I am paranoid about security, but I like having everything up-to-date. The last area I need to tackle is package management. These are packages for tools like git, web browsers, and email clients. I've been using winget for several years. It is not without its problems and is not the only package manager available. I'm betting more than a few readers use Chocolatey. Even though my solution is based on winget, I'm hoping that you can adapt it to your package manager of choice.

Microsoft.Winget.Client

The first step is to make sure I can use PowerShell. Without going into its sordid and tortured history, winget was born as a command-line tool. I can use the native winget command in PowerShell, but it writes text to the pipeline. Parsing text to manage packages is not ideal. Fortunately, Microsoft has released a PowerShell module for winget. The module is called Microsoft.Winget.Client. You can install it from the PowerShell Gallery.

Install-PSResource -Name Microsoft.Winget.Client

The module provides PowerShell commands around a lot of winget functionality. This article isn't about the module, so I won't go into detail, and I think the module needs work. But for my management needs, it is sufficient.

Don't forget that you can use Get-Command to view all the module commands.

Get-Command -Module Microsoft.Winget.Client

My management solution is based onGet-WingetPackage which will retrieve installed package information.

PS C:\> Get-WingetPackage Discord

Name    Id              Version  Available Source
----    --              -------  --------- ------
Discord Discord.Discord 1.0.9005 1.0.9181  winget

You can see that the Available property indicates an update is available. When working with something new, don't assume the default output is all there is. When I run this in PowerShell 7, the Version and Available properties are displayed italicized. This is a hint that these are custom table headers and not actual property name. There may be more to the object than what is displayed.

PS C:\> Get-WingetPackage Discord | Select-Object *

InstalledVersion  : 1.0.9005
Name              : Discord
Id                : Discord.Discord
IsUpdateAvailable : True
Source            : winget
AvailableVersions : {1.0.9181, 1.0.9180, 1.0.9179, 1.0.9178…}

I tell that the IsUpdateAvailable property will be very useful.

PS C:\> Get-WingetPackage | Where IsUpdateAvailable |
Select Name,InstalledVersion,@{Name="Update";Expression={$_.availableVersions[0]}},
source

Name                                            InstalledVersion Update        Source
----                                            ---------------- ------        ------
Microsoft Visual C++ 2005 Redistributable (x64) 8.0.59192        8.0.61000     winget
ESET Endpoint Security                          11.1.2053.2      12.0.2045.0   winget
Microsoft Edge                                  132.0.2957.140   133.0.3065.51 winget
MozBackup                                       Unknown          1.5.1         winget
SpeedFan                                        Unknown          4.52          winget
Microsoft Visual C++ 2012 Redistributable (x86) 11.0.50727.1     11.0.61030.0  winget
DYMO Connect                                    1.4.6.37         1.4.7.48      winget
Python Launcher                                 < 3.12.0         3.12.0        winget
Microsoft .NET SDK 8.0                          8.0.206          8.0.405       winget
Camtasia                                        22.5.2.44147     24.1.5.6542   winget
Discord                                         1.0.9005         1.0.9181      winget
Spotify                                         1.2.12.902.g192… 1.2.56.502.g… winget
Obsidian                                        1.6.7            1.8.4         winget
VSCodium                                        1.96.4.25026     1.97.0.25037  winget
Microsoft Visual Studio Code                    1.96.2           1.97.0        winget
Python 3.10                                     3.10.3           3.10.11       winget

This means I could run a command like this to update everything:

 Get-WingetPackage | Where IsUpdateAvailable | Update-WingetPackage

But I rarely want to take such a wide approach. I prefer more granular control to my updates.

There is also a major bug with some of these commands. The commands to install, update, and uninstall, are supposed to support -WhatIf, but they do not. If I run:

Get-WingetPackage obsidian | Update-WinGetPackage -WhatIf

The package will be downloaded and updated. I can work around this, but you need to be aware of it. This bug has been reported.

Exclusions

Even though winget shows many potential updates, I know from experience that I don't necessarily want to update everything. Some packages, like MozBackup are legacy packages that are no longer being maintained. I can install the package, but there is unlikely to ever be an update. I have a few of these packages installed.

PS C:\> Get-WingetPackage | Where InstalledVersion -eq 'unknown' |
Select Name,InstalledVersion,@{Name="Update";Expression={$_.availableVersions[0]}},
IsUpdateAvailable,Source | Format-Table

Name                               InstalledVersion Update IsUpdateAvailable Source
----                               ---------------- ------ ----------------- ------
Microsoft SQL Server 2019 (64-bit) Unknown                             False
KDiff3 (remove only)               Unknown                             False
MozBackup                          Unknown          1.5.1               True winget
SpeedFan                           Unknown          4.52                True winget

I also have packages like Discord that can update themselves. Finally, there are packages that I know from experience never properly update like Microsoft SDKs or runtimes. This is another ongoing winget problem that I simply live with.

What I've done is create an exclusion text file.

#wingetexclude.txt

#Winget packages to exclude from UpdateWingetPackages.ps1
#This file must be in the same directory as the update script
#Package names will be used in a regex pattern

MuseScore
Discord
Camtasia
Spotify
PowerShell
SDK
Redistributable
Slack
Code

The entries are part of package names that I want to exclude from my update automation. As you can see from the note, the names are using in a wildcard regular expression pattern.

Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
This email brought to you by Buttondown, the easiest way to start and grow your newsletter.