Skip to the main content.

ScriptRunner Blog

How to Use Winget and PowerShell for Efficient App Deployment

Table of Contents 

Post Featured Image

Boost IT efficiency with Winget and PowerShell! Learn how to automate app installations, updates, and management seamlessly. 

During the last few years, Microsoft has been investing resources into its package manager, winget.exe. Winget is a command-line package manager that allows you to install your favorite applications with a single command. Without getting into an unattractive backstory, you can now manage winget with PowerShell. In this post, I'll show you how to use the Microsoft.Winget.Client module to manage winget with PowerShell on your desktop.

 

Getting Ready

You will, of course, need to have winget installed on your machine. The application is a Microsoft open-source project. Winget should be part of newer Windows 11 installations. Although, you most likely will need to update it. Run winget -v to check the version you have installed and compare it to the latest stable release on GitHub.

If you don't have it installed, you can download it from the Microsoft Store, the GitHub releases page, or use PowerShell.


Invoke-WebRequest -Uri https://aka.ms/getwinget -OutFile Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx
Invoke-WebRequest -Uri https://github.com/microsoft/microsoft-uixaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx -OutFile Microsoft.UI.Xaml.2.8.x64.appx
Add-AppxPackage Microsoft.VCLibs.x64.14.00.Desktop.appx
Add-AppxPackage Microsoft.UI.Xaml.2.8.x64.appx
Add-AppxPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle

 

You can run these same commands on Windows 11 to upgrade winget to the latest version. The VCLibs and Ui.Xaml packages are dependencies that may not need to be updated. If you get an error that they are already at the latest version, don't worry about it.

For now, winget should be considered an interactive desktop package management tool. You need at least Windows 10 1809. The only server support it has is on Windows Server 2022 and even that is considered experimental.

I recommend you spend some time using the native winget command-line tool. The PowerShell module is a wrapper around the underlying winget APIs and if you don't understand how winget works, you may find the PowerShell module a bit confusing. This advice goes for anything that has a PowerShell management layer. Know how to use native tools before you start using any PowerShell-equivalent commands.

 

Installing the Module

The PowerShell module for the winget client is also an open-source project. You can find and install the module from the PowerShell Gallery.

Find-Module Microsoft.winget.Client

If you are new to PowerShell you might be prompted to update the NuGet provider. Go ahead and do that.

Nach rechts weisender Zeigefinger (Handrücken)note: Nach rechts weisender Zeigefinger (Handrücken) The module is supported on Windows PowerShell and PowerShell 7.

Install the Module:

Install-Module Microsoft.winget.Client -Scope CurrentUser

Conceptually, winget is a user-specific tool, so I'll use the CurrentUser scope, but it doesn't make any difference. Again, if you are new to PowerShell answer Yes if prompted to install from an untrusted repository.

 

Key Module Commands

The module includes many commands.


PS C:\> Get-Command -Module Microsoft.`winget`.Client | Select Name 

Name
----
Add-WinGetSource
Assert-WinGetPackageManager
Disable-WinGetSetting
Enable-WinGetSetting
Export-WinGetPackage Find-WinGetPackage
Get-WinGetPackage
Get-WinGetSettings
Get-WinGetSource
Get-WinGetUserSettings
Get-WinGetVersion
Install-WinGetPackage
Remove-WinGetSource
Repair-WinGetPackageManager
Reset-WinGetSource
Set-WinGetUserSettings
Test-WinGetUserSettings
Uninstall-WinGetPackage
Update-WinGetPackage

I'm not going to go through all of them, but I'll show you some of the more useful ones.

One thing to be aware of, at least as of the time I am writing this, is that the module's help documentation is minimal.

I know the team behind the module is writing cmdlet help which I hope will be part of the next release. At least the command names are meaningful and follow the PowerShell verb-noun naming convention.


PS C:\> Get-WingetVersion
v1.8.1911
PS C:\> winget -v
v1.8.1911

Remember, the PowerShell commands are wrappers for the underlying winget command-line tool. The PowerShell module doesn't add any features that you can't do with winget. What you do get are PowerShell features like pipeline support and -WhatIf.

This also means that any shortcomings in the command-line tool will be reflected in the PowerShell module.

 

Command: Find-WinGetPackage

You can configure winget to use multiple sources, including the Microsoft Store. For the sake of my demonstrations, I'm going to use the default winget source. Use the Find-WinGetPackage command to search for a package. I want to install the latest PowerShell 7 preview. There are several parameters you can use to filter the results such as -Tag or -Id. Or do what I usually do and use a broad name like PowerShell.

 

From here, I can narrow my search and confirm the package I want to install.

Find-WinGetPackage -id Microsoft.PowerShell.Preview -Source winget

Unlike winget which gives you text, the PowerShell module returns objects with defined properties.


PS C:\> Find-WinGetPackage -id Microsoft.PowerShell.Preview -Source winget | Select *
Version : 7.5.0.3
Name : PowerShell Preview
Id : Microsoft.PowerShell.Preview
IsUpdateAvailable : False
Source : winget
AvailableVersions : {7.5.0.3, 7.5.0.2, 7.5.0.1, 7.4.0.101...}

 

When searching for packages you have to be careful. Even though PowerShell isn't case-sensitive by default, winget can be touchy. Using the native command you might try this:


PS C:\> winget find --id microsoft.windowsterminal --source winget
Name Id Version
-----------------------------------------------------------------------
Windows Terminal Microsoft.WindowsTerminal 1.20.11781.0
Windows Terminal Preview Microsoft.WindowsTerminal.Preview 1.21.1772.0

 

You don't want the preview version so you try to find it with PowerShell.


PS C:\> Find-WinGetPackage -id Microsoft.WindowsTerminal
Name Id Version Source
---- -- ------- ------
Windows Terminal Microsoft.WindowsTerminal 1.20.11781.0 winget
Windows Terminal Preview Microsoft.WindowsTerminal.Preview 1.21.1772.0 winget

 

This isn't as specific as you might think. You need to include the Equal parameter, as you would in the native command.


PS C:\> winget find --id microsoft.windowsterminal --source winget --exact No package found matching input criteria.
PS C:\> winget find --id Microsoft.WindowsTerminal --source winget --exact Name Id Version
------------------------------------------------------- Windows Terminal Microsoft.WindowsTerminal 1.20.11781.0

 

Notice that casing matters.


PS C:\> Find-WinGetPackage -id Microsoft.WindowsTerminal -MatchOption Equals

Name Id Version Source
---- -- ------- ------
Windows Terminal Microsoft.WindowsTerminal 1.20.11781.0 winget

 

Also, in the world of winget, there is a distinction between the package you install and its manifest. You can learn more about the package from its manifest with the show command.

winget show --id Microsoft.PowerShell.Preview --source winget

 

Command: Install-WinGetPackage

Once you've found the package you want, bring the find command back to your prompt and pipe it to Install‑WinGetPackage.

Find-WinGetPackage -id Microsoft.PowerShell.Preview -Source winget | InstallWinGetPackage

I find it best to be as specific as possible when installing. I like using the -Id parameter to specify the package I want to install. This way I can be sure I'm getting the package I want.

Install-WinGetPackage -Id Dropbox.Dropbox -Source winget

 

Warnung  warning  Warnung
There is one major issue with the Install-WinGetPackage command that has been filed as a GitHub issue. Even though the command says it supports -WhatIf, it doesn't. If I try to run Install‑WinGetPackage ‑Id Dropbox.Dropbox ‑Source winget ‑Verbose ‑WhatIf, the command will install the package. I have to believe this will be fixed in a future release since it is fundamental to the PowerShell paradigm.

 

Command: Get-WinGetPackage

It is no mystery that Get-WinGetPackage will show you all installed packages. Although by default, the command will also show installed AppX packages like "Phone Link" and "Notepad". Even though there is a Source parameter, it doesn't seem to work. You can filter the results with Where‑Object.

Get-WinGetPackage | Where Source -eq 'winget' | Select Name,ID,InstalledVersion

 

As before, if you want a specific package you need to be careful with the casing.


PS C:\> Get-WinGetPackage -Name PowerShell -MatchOption Equals
Name Id Version Available Source
---- -- ------- --------- ------
PowerShell Microsoft.PowerShell 7.4.4.0 winget

 

Command: Update-WinGetPackage

Over time, packages will need to be updated. The output of Get-WinGetPackage has a convenient property you can use.


PS C:\> Get-WinGetPackage | Where {$_.Source -eq 'WinGet' -AND $_.IsUpdateAvailable}
Name Id Version Available Source
---- -- ------- --------- ------
GitHub CLI GitHub.cli 2.52.0 2.54.0 winget
Dev Home (Preview) Microsoft.DevHome 0.1600.561.0 0.1601.561.0 winget
Windows Terminal Microsoft.WindowsTerminal 1.12.10983.0 1.20.11781.0 winget

 

You can't simply run Update-WinGetPackage and expect it to update everything automatically. You need to be specific.

Get-WinGetPackage | Where {$_.Source -eq 'WinGet' -AND $_.IsUpdateAvailable}

The package updates should be silent and unattended. At the end of the process, the command will give you a summary result.

 

Totenkopf mit gekreuzten Knochen beware Totenkopf mit gekreuzten Knochen
Unfortunately, the Update-WinGetPackage command suffers the same flaw as Install-WingetPackage.
The syntax indicates it supports -WhatIf, but it doesn't.

 

Command: Uninstall-WinGetPackage

To remove a package, use the Uninstall-WinGetPackage command. You can use the Get-WinGetPackage command to find the package you want to remove. As with the other commands, you need to be specific.


PS C:\> Uninstall-WinGetPackage -id Git.Git -MatchOption Equals
Id Name Source UninstallerErrorCode Status RebootRequired ExtendedErrorCode CorrelationData
-- ---- ------ -------------------- ------ -------------- ----------------- --- ------------
Git.Git Git winget 0 Ok False

If there is an error with the uninstall, you won't get a PowerShell error, you'll get something in one or more of the output errors. PowerShell will give you an exception if the Uninstall-WinGetPackage command fails, such as if it can't find the package.

 

Scripting WinGet Management

Where this module is worth your time, despite the shortcomings, is in scripting. You can use the module to automate the management of your desktop applications. This is useful when building test systems. Or if you want a way to ensure your desktop is configured the way you need it. It is much easier to script with PowerShell than to use the native winget command-line tool.

Let me give you a few examples. First, I created a JSON file with the packages I want to make sure are installed and up-to-date. Run a command like this to create the file.


Get-WingetPackage | Where-Object { $_.Source -eq 'winget' } |
Select-Object Name,ID | ConvertTo-Json |
Out-File -FilePath D:\temp\winget.json -Encoding utf8

Edit the file to include only the packages you want to install.
I'm using JSON but you can use whatever format works for you.


[
{
"Name": "Git", "Id": "Git.Git"
},
{
"Name": "Mozilla Firefox", "Id": "Mozilla.Firefox"
},
{
"Name": "Mozilla Thunderbird", "Id": "Mozilla.Thunderbird"
},
...

 

I have this script that will read the JSON file and install the packages if not already installed. If the package is installed, then it will check for updates.


#requires -version 5.1 #requires -RunAsAdministrator #requires -module Microsoft.Winget.Client

#WingetMaster.ps1

[cmdletbinding(SupportsShouldProcess)]
Param(
[Parameter(HelpMessage = 'Path to the winget.json file')]
[ValidateScript({ Test-Path $_ })]
[string]$Path = '.\winget.json'
)

#Piping converted Output to fix a formatting bug in Windows PowerShell
$find = Get-Content -Path $Path -Encoding utf8 |
ConvertFrom-Json |
foreach { $_ } |
Find-WinGetPackage -Source winget -match Equals

foreach ($pkg in $find) {
$r = Get-WinGetPackage -Id $pkg.Id -MatchOption Equals
if ($null -eq $r) {
#Install the package if not already installed
Write-Host "Installing $($pkg.Id)" -ForegroundColor Cyan

#use my own WhatIf code
if ($PSCmdlet.ShouldProcess($pkg.Id, 'Install-WinGetPackage')) {
Install-WinGetPackage -Id $pkg.Id
}
}
elseif ($r.IsUpdateAvailable) {
#update the package if a new version is available
Write-Host "Updating $($pkg.Id)" -ForegroundColor Yellow
if ($PSCmdlet.ShouldProcess($pkg.Id, 'Update-WinGetPackage')) {
Update-WinGetPackage -Id $pkg.Id
}
}
else {
Write-Host "$($pkg.Id) is already installed" -ForegroundColor Green
}
} #foreach

Write-Host "$($MyInvocation.MyCommand) completed" -ForegroundColor Green

 

My code adds support for WhatIf that works.

You can run this as often as you like. It won't install anything that is already installed or up-to-date.

I also have a tool I use to update selected packages. This script requires PowerShell 7 and the Microsoft.PowerShell.ConsoleGuiTools module.


#requires -version 7.3
#requires -Module Microsoft.Winget.Client
#requires -module Microsoft.PowerShell.ConsoleGuiTools

#UpdateWingetPackages.ps1

[CmdletBinding()]
Param()
#17 Jan 2024 Moved exclusions to an external file
[string]$Exclude = (Get-Content $PSScriptRoot\WingetExclude.txt | Where-Object {$_ -notMatch "^#" -AND $_ -match "\w+"}) -join "|"

#10 Jan 2024 invoke updates in parallel
$sb = {
Param($MyExclude)
Write-Progress "[$((Get-Date).TimeOfDay)] Checking for Winget package updates"
Get-WinGetPackage -Source Winget |
Where-Object {$_.Source -eq 'winget' -AND
$_.IsUpdateAvailable -AND ($_.InstalledVersion -notMatch "unknown|\<") -AND ($_.Name -notMatch $myExclude)} |
Out-ConsoleGridView -Title "Select Winget packages to upgrade" -OutputMode Multiple |

Foreach-Object -Parallel {
$Name = $_.Name
Write-Host "[$((Get-Date).TimeOfDay)] Updating $($_.Name)" -ForegroundColor Green
# 22 April 2024 added error handling to write a meaningful exception message
Try {
Update-WinGetPackage -mode Silent -ID $_.ID -Source Winget -ErrorAction Stop |
Select-Object @{Name="Package";Expression= {$Name}},RebootRequired,InstallerErrorCode,Status
}
Catch {
Write-Warning "Failed to update $Name. $($_.Exception.Message)"
}
}
}
Try {
#verify Winget module commands will run. There may be assembly conflicts with the ConsoleGuiTools module
$ver = Get-WinGetVersion -ErrorAction stop
Invoke-Command -ScriptBlock $sb -ArgumentList $Exclude
}
Catch {
#write-warning $_.Exception.message
#run the task in a clean PowerShell session to avoid assembly conflicts
pwsh -NoLogo -NoProfile -command $sb -args $Exclude
}

 

I have several packages that either I want to keep at a given version, don't want to update because of compatibility issues, or prefer to manage updates by other means. My script uses an exclude file to define these packages.



#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
Python
ESET
Discord
Camtasia
Dymo
Spotify
PowerShell
FoxIt

When I run the script, packages with available updates are displayed using Out-ConsoleGridView.

I can select the packages I want to update and click OK. The other reason I'm using PowerShell 7 is that I can run the updates in parallel. This is a great way to update multiple packages quickly.

 

Conclusion

I am hoping for the next release of the Microsoft.Winget.Client module will include more robust help documentation and resolve a few bugs. The module is a great way to automate the management of your desktop applications if you are a winget user. You can use it to ensure your desktop is configured the way you need it or use it to quickly configure lab or test machines. Winget is still primarily a desktop tool. I know there is a Winget DSC resource in the works, that could address server needs, but that's a topic for another post.

I encourage you to try the module and bang on it. File issues and start discussions on the GitHub repository. The more feedback the team gets, the better the module will become and we'll all benefit.

 

all of our PowerShell cheat sheets for you, incl. Active Directory, Graph, Teams, Exchange and more on Security

Your ultimate PowerShell Cheat Sheet

Unleash the full potential of PowerShell with our handy poster. Whether you're a beginner or a seasoned pro, this cheat sheet is designed to be your go-to resource for the most important and commonly used cmdlets.

The poster is available for download and in paper form.

PowerShell Poster 2023

Get your poster here!

 

 

Related links 

 

Related posts

14 min read

How to Use Winget and PowerShell for Efficient App Deployment

Boost IT efficiency with Winget and PowerShell! Learn how to automate app installations, updates, and management...

9 min read

Streamline data governance with PowerShell: What is Microsoft Purview for you?

What is Microsoft Purview for you? Do you already master data governance with it? Learn how PowerShell simplifies...

14 min read

Graph PowerShell and Microsoft Teams – Part 3 of our series

Damian shares his knowledge about Microsoft Graph. His third article goes into detail about teams and introduces the...

About the author: