Skip to content

Instantly share code, notes, and snippets.

@inxomnyaa
Last active August 9, 2025 23:52
Show Gist options
  • Save inxomnyaa/0ee29d68a6b4c92ba53810828dbc0f4a to your computer and use it in GitHub Desktop.
Save inxomnyaa/0ee29d68a6b4c92ba53810828dbc0f4a to your computer and use it in GitHub Desktop.
Upgrade Selected Apps via Winget
# Based on https://www.codewrecks.com/post/general/winget-update-selective/ (gist at https://gist.github.com/alkampfergit/2f662c07df0ca379c8e8e65e588c687b)
# Adds a grid for selecting multiple apps
# Fixes packages with truncated names
# Adds a confirmation dialogue before actually upgrading
# Updated to use newer winget commands and improved error handling
# Improved Winget Selective Upgrade Script using Microsoft.WinGet.Client PowerShell Module
# This version uses structured PowerShell objects instead of parsing text output, which is way faster
class Software {
[string]$Name
[string]$Id
[string]$Version
[string]$AvailableVersion
}
# Check if Microsoft.WinGet.Client module is available
$useFallback = $false
if (-not (Get-Module -ListAvailable -Name Microsoft.WinGet.Client)) {
Write-Host "Microsoft.WinGet.Client module not found. Installing..." -ForegroundColor Yellow
try {
# Try with -AllowPrerelease first (newer PowerShellGet)
try {
Install-Module -Name Microsoft.WinGet.Client -Force -AllowPrerelease -Scope CurrentUser
}
catch {
# Fallback for older PowerShellGet versions
Write-Host "Trying without -AllowPrerelease parameter..." -ForegroundColor Yellow
Install-Module -Name Microsoft.WinGet.Client -Force -Scope CurrentUser
}
Write-Host "Module installed successfully!" -ForegroundColor Green
}
catch {
Write-Warning "Failed to install Microsoft.WinGet.Client module: $($_.Exception.Message)"
Write-Host ""
Write-Host "Manual installation options:" -ForegroundColor Cyan
Write-Host "1. Run PowerShell as Administrator and try: Install-Module -Name Microsoft.WinGet.Client -Force" -ForegroundColor White
Write-Host "2. Or install via winget: winget install Microsoft.PowerShell.PSResourceGet" -ForegroundColor White
Write-Host "3. Update PowerShellGet: Install-Module PowerShellGet -Force" -ForegroundColor White
Write-Host ""
Write-Host "Falling back to traditional winget parsing method..." -ForegroundColor Yellow
$useFallback = $true
}
}
# Import the module if available, otherwise use fallback
if (-not $useFallback) {
try {
Import-Module Microsoft.WinGet.Client -Force
Write-Host "Using Microsoft.WinGet.Client PowerShell module" -ForegroundColor Green
}
catch {
Write-Warning "Failed to import Microsoft.WinGet.Client module: $($_.Exception.Message)"
Write-Host "Using fallback parsing method..." -ForegroundColor Yellow
$useFallback = $true
}
}
Write-Host "Checking for available upgrades..."
# Try PowerShell module first if available
$upgradeList = @()
if (-not $useFallback) {
try {
# Get all installed packages that have upgrades available
$installedPackages = Get-WinGetPackage | Where-Object {
$_.IsUpdateAvailable -eq $true
}
Write-Host "PowerShell module found $($installedPackages.Count) packages with available updates"
# If we got very few results, suggest fallback for unknown versions
if ($installedPackages.Count -eq 0) {
Write-Host "No packages found via PowerShell module." -ForegroundColor Yellow
Write-Host "This might be due to packages with unknown versions not being detected." -ForegroundColor Yellow
Write-Host "Falling back to traditional parsing to include --include-unknown packages..." -ForegroundColor Yellow
$useFallback = $true
} else {
# Convert to our Software class for consistency
foreach ($pkg in $installedPackages) {
$software = [Software]::new()
$software.Name = $pkg.Name
$software.Id = $pkg.Id
$software.Version = if ($pkg.InstalledVersion) { $pkg.InstalledVersion } else { "Unknown" }
$software.AvailableVersion = if ($pkg.AvailableVersions -and $pkg.AvailableVersions.Count -gt 0) {
$pkg.AvailableVersions[0]
} else {
"Unknown"
}
$upgradeList += $software
}
}
}
catch {
Write-Warning "Failed to get package information via PowerShell module: $($_.Exception.Message)"
Write-Host "Falling back to traditional parsing method..." -ForegroundColor Yellow
$useFallback = $true
}
}
# If we're using fallback or PowerShell module found no packages, use traditional parsing
if ($useFallback) {
Write-Host "Using traditional winget parsing method (includes --include-unknown packages)..."
try {
$upgradeResult = winget list --upgrade-available --include-unknown | Out-String
}
catch {
Write-Error "Failed to get upgrade list from winget: $($_.Exception.Message)"
Read-Host -Prompt "Press any key to exit"
exit 1
}
# Check if we have any content to process
if ([string]::IsNullOrWhiteSpace($upgradeResult)) {
Write-Host "No upgrade information available from winget."
Read-Host -Prompt "Press any key to exit"
exit 0
}
$lines = $upgradeResult.Split([Environment]::NewLine)
# Find the line that starts with Name, it contains the header
$fl = 0
while ($fl -lt $lines.Length -and -not $lines[$fl].StartsWith("Name"))
{
$fl++
}
# Check if we found a header line
if ($fl -ge $lines.Length) {
Write-Host "No packages available for upgrade or unexpected winget output format."
Read-Host -Prompt "Press any key to exit"
exit 0
}
# Parse the header positions
$headerLine = $lines[$fl]
$idStart = $headerLine.IndexOf("Id")
$versionStart = $headerLine.IndexOf("Version")
$availableStart = $headerLine.IndexOf("Available")
$sourceStart = $headerLine.IndexOf("Source")
# Validate required columns
if ($idStart -eq -1 -or $versionStart -eq -1 -or $availableStart -eq -1) {
Write-Error "Could not find required columns in winget output"
Read-Host -Prompt "Press any key to exit"
exit 1
}
if ($sourceStart -eq -1) {
$sourceStart = $headerLine.Length
}
# Parse packages from traditional output
For ($i = $fl + 1; $i -lt $lines.Length; $i++)
{
$line = $lines[$i]
if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith('-')) {
continue
}
if ($line.Length -gt $availableStart)
{
try {
$name = $line.Substring(0, $idStart).TrimEnd()
$id = $line.Substring($idStart, $versionStart - $idStart).TrimEnd()
if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($id)) {
continue
}
$version = $line.Substring($versionStart, $availableStart - $versionStart).TrimEnd()
$available = $line.Substring($availableStart, $sourceStart - $availableStart).TrimEnd()
$software = [Software]::new()
$software.Name = $name
$software.Id = $id
$software.Version = $version
$software.AvailableVersion = $available
$upgradeList += $software
}
catch {
Write-Warning "Failed to parse line $i`: '$line'. Error: $($_.Exception.Message)"
continue
}
}
}
}
# Check if we have any packages to process
if (-not $upgradeList -or $upgradeList.Count -eq 0) {
Write-Host "No packages available for upgrade." -ForegroundColor Green
Read-Host -Prompt "Press any key to exit"
exit 0
}
Write-Host "Found $($upgradeList.Count) packages available for upgrade:" -ForegroundColor Green
$upgradeList | Format-Table
Write-Host "Select packages to upgrade in the grid view..."
$toUpgrade = $upgradeList | Out-GridView -PassThru -Title "Select packages to upgrade"
if (-not $toUpgrade -or $toUpgrade.Count -eq 0) {
Write-Host "No packages selected for upgrade."
Read-Host -Prompt "Press any key to exit"
exit 0
}
Write-Host "Selected $($toUpgrade.Count) packages for upgrade."
$upgraded = 0
$failed = 0
foreach ($package in $toUpgrade)
{
# Confirmation dialogue
$title = "Upgrade $($package.Id)?"
$question = "Upgrade package '$($package.Name)' ($($package.Id)) from $($package.Version) to $($package.AvailableVersion)?"
$choices = '&Yes', '&No', '&All'
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 0)
if ($decision -eq 0 -or $decision -eq 2) {
Write-Host "Upgrading package $($package.Id)..."
try {
# Use --wait only for individual "Yes" selections, not for "All"
if ($decision -eq 0) {
& winget upgrade --include-unknown --wait --verbose $package.Id
} else {
& winget upgrade --include-unknown --verbose $package.Id
}
if($LASTEXITCODE -eq 0){
Write-Host "Successfully upgraded $($package.Id)" -ForegroundColor Green
$upgraded++
}
else{
Write-Host "Failed to upgrade $($package.Id) (exit code: $LASTEXITCODE)" -ForegroundColor Red
$failed++
}
}
catch {
Write-Host "Error upgrading $($package.Id): $($_.Exception.Message)" -ForegroundColor Red
$failed++
}
# If "All" was selected, process remaining packages automatically
if ($decision -eq 2) {
# Get current package index to process remaining packages
$currentIndex = $toUpgrade.IndexOf($package)
for ($j = $currentIndex + 1; $j -lt $toUpgrade.Count; $j++) {
$remainingPackage = $toUpgrade[$j]
Write-Host "Auto-upgrading package $($remainingPackage.Id)..."
try {
& winget upgrade --include-unknown --verbose $remainingPackage.Id
if($LASTEXITCODE -eq 0){
Write-Host "Successfully upgraded $($remainingPackage.Id)" -ForegroundColor Green
$upgraded++
}
else{
Write-Host "Failed to upgrade $($remainingPackage.Id) (exit code: $LASTEXITCODE)" -ForegroundColor Red
$failed++
}
}
catch {
Write-Host "Error upgrading $($remainingPackage.Id): $($_.Exception.Message)" -ForegroundColor Red
$failed++
}
}
break # Exit the main loop since we processed all remaining packages
}
}
else {
Write-Host "Skipped upgrade for package $($package.Id)" -ForegroundColor Yellow
}
}
Write-Host ""
Write-Host "Upgrade Summary:" -ForegroundColor Cyan
Write-Host " Successfully upgraded: $upgraded" -ForegroundColor Green
Write-Host " Failed: $failed" -ForegroundColor Red
Write-Host " Total selected: $($toUpgrade.Count)" -ForegroundColor White
Read-Host -Prompt "Press any key to exit"
@chriscollingwood613
Copy link

Suggest more error checking on the first parse of the winget output at line 40 to handle the case where there is nothing to process and the output doesn't contain an expected string which causes the script to spew several lines of error messages.

@inxomnyaa
Copy link
Author

Suggest more error checking on the first parse of the winget output at line 40 to handle the case where there is nothing to process and the output doesn't contain an expected string which causes the script to spew several lines of error messages.

@chriscollingwood613 thanks for the suggestion, i implemented it now and switched to winget list --upgrade-available. i am still working on some enhancements and speed improvements

@inxomnyaa
Copy link
Author

based on this comment microsoft/winget-cli#4743 (comment) which suggests "If you use the PowerShell Module, Get-WinGetPackage will return structured data that includes all the information and can be converted to JSON using other inbuilt PowerShell functions." it seems right now the only option is to install a module, but i feel like this kind of is bloat, when it could just be untruncated. It is faster than parsing text though ^^"

BUT ive tested it just now, and my current version doesnt list the packages with unknown version 💀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment