With the Windows Package Manager (winget), it is easy to install software from the command line. This makes your installs scriptable and thus repeatable. This article is the WinGet counterpart to my previous Notes on Chocolatey: it shows how to reproduce a machine's installation instructions and update your packages regularly.
Install WinGet
WinGet is supported on Windows 11, Windows 10 version 1809 (build 17763) and later, and Windows Server 2025. On Windows desktop, it is delivered as part of App Installer through the Microsoft Store. On a normal, up-to-date Windows 10 or Windows 11 installation, winget is usually already available. Check it from PowerShell:
winget --version
winget source updateIf winget is missing or broken, update App Installer from the Microsoft Store. The PowerShell repair command can also install the latest WinGet client:
Repair-WinGetPackageManager -Latest -ForceListing installation instructions
Let's generate installation instructions from the applications on a machine. WinGet exports the packages it recognizes to JSON, which we can turn into individual install commands:
& {
$exportPath = Join-Path $env:TEMP "winget-packages-$([guid]::NewGuid()).json"
try {
$exportOutput = & winget export --output $exportPath --disable-interactivity 2>&1
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
throw "WinGet export failed (exit code ${exitCode}):`n$($exportOutput -join [Environment]::NewLine)"
}
$export = Get-Content -LiteralPath $exportPath -Raw |
ConvertFrom-Json -ErrorAction Stop
foreach ($source in ($export.Sources | Sort-Object { $_.SourceDetails.Name })) {
$sourceName = $source.SourceDetails.Name.Replace("'", "''")
foreach ($package in ($source.Packages | Sort-Object PackageIdentifier)) {
$packageId = $package.PackageIdentifier.Replace("'", "''")
'winget install --id ''{0}'' --exact --source ''{1}''' -f $packageId, $sourceName
}
}
}
finally {
Remove-Item -LiteralPath $exportPath -Force -ErrorAction SilentlyContinue
}
}
This prints commands you can copy into an installation script; it does not install anything. Each command includes the package's source, so Microsoft Store packages keep --source 'msstore'.
Upgrade Task
Installing packages is one thing, upgrading them regularly is another. Let's create a weekly Windows Task Scheduler task for upgrading. It uses an elevated PowerShell session to do so:
& {
$day = 'Monday'
$time = '9:30AM'
$taskName = 'UpgradeWingetPackages'
$scriptPath = Join-Path $env:LOCALAPPDATA 'Upgrade-WingetPackages.ps1'
$upgradeScript = @'
winget source update
if ($LASTEXITCODE -ne 0) {
throw "WinGet source update failed (exit code $LASTEXITCODE)."
}
winget upgrade --all `
--silent `
--disable-interactivity `
--accept-source-agreements `
--accept-package-agreements
if ($LASTEXITCODE -ne 0) {
throw "WinGet upgrade failed (exit code $LASTEXITCODE)."
}
'@
Set-Content -LiteralPath $scriptPath -Value $upgradeScript -Encoding UTF8
$quotedScriptPath = $scriptPath.Replace("'", "''")
$launcher = @"
`$arguments = '-NoProfile -ExecutionPolicy Bypass -File `"$quotedScriptPath`"'
`$process = Start-Process -FilePath 'powershell.exe' -ArgumentList `$arguments -Verb RunAs -Wait -PassThru
exit `$process.ExitCode
"@
$encodedLauncher = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($launcher))
$action = New-ScheduledTaskAction `
-Execute 'powershell.exe' `
-Argument "-NoProfile -EncodedCommand $encodedLauncher"
$trigger = New-ScheduledTaskTrigger `
-Weekly `
-DaysOfWeek $day `
-At $time
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-RunOnlyIfNetworkAvailable
$principal = New-ScheduledTaskPrincipal `
-UserId ([Security.Principal.WindowsIdentity]::GetCurrent().Name) `
-LogonType Interactive `
-RunLevel Limited
$taskExists = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($taskExists) {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
Register-ScheduledTask `
-Action $action `
-Trigger $trigger `
-TaskName $taskName `
-Description 'Upgrade all WinGet packages' `
-Settings $settings `
-Principal $principal
}
Make Upgrade Task Compatible with MakeMeAdmin
At Wehkamp we use Make Me Admin. You need to claim admin rights before running administrative tasks. The regular UAC prompt does not let you claim those rights first.
This script updates the task action to launch Make Me Admin, waits for you to request temporary admin rights, and then start the elevated upgrade:
& {
$taskName = 'UpgradeWingetPackages'
$taskExists = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if (-not $taskExists) {
throw "Task '$taskName' does not exist. Run upgrade-task.ps1 first."
}
$makeMeAdminDir = Join-Path $env:ProgramFiles 'Make Me Admin'
$makeMeAdminPath = Join-Path $makeMeAdminDir 'MakeMeAdminUI.exe'
$scriptPath = Join-Path $env:LOCALAPPDATA 'Upgrade-WingetPackages.ps1'
if (-not (Test-Path -LiteralPath $makeMeAdminPath)) {
throw "Make Me Admin was not found at '$makeMeAdminPath'."
}
if (-not (Test-Path -LiteralPath $scriptPath)) {
throw "The WinGet upgrade script was not found at '$scriptPath'. Run upgrade-task.ps1 first."
}
$quotedMakeMeAdminDir = $makeMeAdminDir.Replace("'", "''")
$quotedScriptPath = $scriptPath.Replace("'", "''")
$launcher = @"
Set-Location -LiteralPath '$quotedMakeMeAdminDir'
& (Join-Path '$quotedMakeMeAdminDir' 'MakeMeAdminUI.exe')
Read-Host 'Make sure you are admin before proceeding...'
`$arguments = '-NoProfile -ExecutionPolicy Bypass -File `"$quotedScriptPath`"'
`$process = Start-Process -FilePath 'powershell.exe' -ArgumentList `$arguments -Verb RunAs -Wait -PassThru
exit `$process.ExitCode
"@
$encodedLauncher = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($launcher))
$newAction = New-ScheduledTaskAction `
-Execute 'powershell.exe' `
-Argument "-NoProfile -EncodedCommand $encodedLauncher"
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
Register-ScheduledTask `
-Action $newAction `
-Trigger $taskExists.Triggers `
-TaskName $taskName `
-Description 'Upgrade all WinGet packages with Make Me Admin' `
-Settings $taskExists.Settings `
-Principal $taskExists.Principal
}
Skip an upgrade
WinGet supports pins for packages that should not be upgraded automatically. For example:
winget pin add --id Git.Git
winget pin list
winget pin remove --id Git.GitA package with a non-blocking pin can still be included explicitly when upgrading everything:
winget upgrade --all --include-pinnedMy arbitrary list of installed packages
I use my articles as notes to my future self. When I get a new machine, I usually install the following packages.
& {
# development tools
## AWS CLI and credentials management for AWS development
winget install Amazon.AWSCLI ByteNess.AWSVault --exact
## Kiro AI development environment
winget install Amazon.Kiro --exact
## Docker desktop alternative and image layer inspection
winget install SUSE.RancherDesktop wagoodman.dive --exact
## Build and deploy cloud-native applications
winget install Google.ContainerTools.Skaffold --exact
## Git, GitHub, signing, and SSH tools
winget install Git.Git GitHub.cli GnuPG.Gpg4win xhcoding.sshpass-win32 --exact
## Edit YAML and JSON files
winget install MikeFarah.yq jqlang.jq --exact
## Node.js version management for Windows
winget install CoreyButler.NVMforWindows --exact
## .NET 10 SDK
winget install Microsoft.DotNet.SDK.10 --exact
## IDEs and development assistants
winget install Microsoft.VisualStudioCode SST.opencode StablyAI.Orca --exact
## Compare files and directories
winget install ScooterSoftware.BeyondCompare.5 --exact
## GNU-compatible command-line utilities for Windows
winget install Microsoft.Coreutils ezwinports.make --exact
# image and video tools
## Raster image and SVG editing
winget install dotPDN.PaintDotNet Inkscape.Inkscape --exact
## Create diagrams
winget install JGraph.Draw --exact
## Screen recording and video playback
winget install NickeManarin.ScreenToGif VideoLAN.VLC --exact
## FFmpeg tools for video processing and yt-dlp
winget install Gyan.FFmpeg yt-dlp.FFmpeg yt-dlp.yt-dlp --exact
# tools
## Browse the web
winget install Brave.Brave --exact
## Compare and copy files
winget install CodeSector.TeraCopy --exact
## Find what is using disk space
winget install JAMSoftware.TreeSize.Free --exact
## Configure Logitech peripherals
winget install Logitech.Options --exact
## PowerShell and Windows productivity tools
winget install Microsoft.PowerShell Microsoft.PowerToys --exact
## OpenSSL command-line toolkit for working with certificates and secrets
winget install ShiningLight.OpenSSL.Light --exact
# other
## Manage and update Ravensburger tiptoi devices
winget install RavensburgerAG.tiptoiManager --exact
## VPN client
winget install SparkLabs.Viscosity --exact
# Microsoft Store apps
## Daily Bing and Windows Spotlight wallpapers
winget install --id 9NBLGGH1ZBKW --exact --source msstore
## WhatsApp
winget install --id 9NKSQGP7F2NH --exact --source msstore
## ChatGPT
winget install --id 9PLM9XGG6VKS --exact --source msstore
## Spotify
winget install --id 9NCBCSZSJRSB --exact --source msstore
}
Beyond Compare has separate WinGet packages for versions 4 and 5. I used version 5 above. Package availability can change; use winget search to check before installing.
I like the fact that we can also directly install packages I normally install from the store.
Post installation
After installing the tools, you might want to install a version of Node.js and some global dependencies. These are my go-to global dependencies:
& {
# download and use Node.js 24 LTS
nvm install 24
nvm use 24
# enable ncu (npm-check-updates) globally for
# easy updating of node applications:
npm install --global npm-check-updates
}
Enjoy!
The code is on GitHub, so check it out: 18.winget.