Skip to content

Instantly share code, notes, and snippets.

@alkampfergit
Last active January 14, 2024 19:38
Show Gist options
  • Save alkampfergit/19f89c1a93cc1e7b9ec9bf501f2b9134 to your computer and use it in GitHub Desktop.
Save alkampfergit/19f89c1a93cc1e7b9ec9bf501f2b9134 to your computer and use it in GitHub Desktop.
Winget upgrade output parsed into a real Powershell Object
class Software {
[string]$Name
[string]$Id
[string]$Version
[string]$AvailableVersion
}
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$upgradeResult = winget upgrade | Out-String
$lines = $upgradeResult.Split([Environment]::NewLine)
# Find the line that starts with Name, it contains the header
$fl = 0
while (-not $lines[$fl].StartsWith("Name"))
{
$fl++
}
# Line $i has the header, we can find char where we find ID and Version
$idStart = $lines[$fl].IndexOf("Id")
$versionStart = $lines[$fl].IndexOf("Version")
$availableStart = $lines[$fl].IndexOf("Available")
$sourceStart = $lines[$fl].IndexOf("Source")
# Now cycle in real package and split accordingly
$upgradeList = @()
For ($i = $fl + 1; $i -le $lines.Length; $i++)
{
$line = $lines[$i]
if ($line.Length -gt ($availableStart + 1) -and -not $line.StartsWith('-'))
{
$name = $line.Substring(0, $idStart).TrimEnd()
$id = $line.Substring($idStart, $versionStart - $idStart).TrimEnd()
$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
}
}
$upgradeList | Get-Member
$upgradeList | Format-Table
@ionred
Copy link

ionred commented Jul 4, 2023

Might I suggest adding

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

near the top to account for issues that may occur when running this in PS5.1? An example of before and after running that command, if using the default output encoding of ascii:
image
image

@alkampfergit
Copy link
Author

Done!

@Warrentheo
Copy link

Warrentheo commented Sep 13, 2023

My output includes the following line at the end:
2 package(s) have version numbers that cannot be determined. Use --include-unknown to see all results.
and it doesn't get handled correctly,

For ($i = $fl + 1; $i -le $lines.Length; $i++)
{
$line = $lines[$i]
if ($line.Length -gt ($availableStart + 1) -and -not $line.StartsWith('-') -and -not $line.Contains("package(s) have version numbers"))
{

Here is the suggested edit that just ignores these lines

@houtianze
Copy link

houtianze commented Jan 14, 2024

Thanks for the usefl script. I made some changes:

  1. Added --include-unknown to the winget upgrade command to avoid the issue mentioned below
  2. Becasue some of the software installed on my system has non-ascii name, I worked around the quirks
  3. Made this code snippet to a function. (This is just for my use case)
function Get-Winget-Upgradables
{

  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
  $upgradeResult = winget upgrade --include-unknown | Out-String

  $lines = $upgradeResult.Split([Environment]::NewLine)


  # Find the line that starts with Name, it contains the header
  $fl = 0
  while (-not $lines[$fl].StartsWith("Name"))
  {
    $fl++
  }

  # Line $i has the header, we can find char where we find ID and Version
  $idStart = $lines[$fl].IndexOf("Id")
  $versionStart = $lines[$fl].IndexOf("Version")
  $availableStart = $lines[$fl].IndexOf("Available")
  $sourceStart = $lines[$fl].IndexOf("Source")

  # Now cycle in real package and split accordingly
  $upgradeList = @()
  For ($i = $fl + 1; $i -le $lines.Length; $i++) 
  {
    $line = $lines[$i]
    if ($line.Length -gt ($sourceStart + 1) -and -not $line.StartsWith('-'))
    {
      # when the name of the software is non-ascii, rune counting becomes messy.
      $nameWithSpaceEnding = $line.Substring(0, $idStart)
      $nameWithSpaceEndingByteCount = [System.Text.Encoding]::UTF8.GetByteCount($nameWithSpaceEnding)
      $nameOffset = ($nameWithSpaceEndingByteCount - $nameWithSpaceEnding.Length) / 2;
      $name = $line.Substring(0, $idStart - $nameOffset).Trim()
      # need to Trim() instead of TrimEnd() because somehow when run with | Out-String,
      # the winget output of name becomse ellipsized... Trim() would work around this.
      $id = $line.Substring($idStart - $nameOffset, $versionStart - $idStart).Trim()
      $version = $line.Substring($versionStart - $nameOffset, $availableStart - $versionStart).Trim()
      $available = $line.Substring($availableStart - $nameOffset, $sourceStart - $availableStart).Trim()
      $software = [WingetSoftware]::new()
      $software.Name = $name;
      $software.Id = $id;
      $software.Version = $version
      $software.AvailableVersion = $available;

      $upgradeList += $software
    }
  }

  return $upgradeList;
}

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