Skip to content

Instantly share code, notes, and snippets.

@alkampfergit
Last active December 8, 2024 12:57
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
@F4Jonatas
Copy link

F4Jonatas commented Nov 20, 2024

@houtianze I tried to run your function, but it generates an error.

Line |
 1   |    while (-not $lines[$fl].StartsWith("Name"))
     |    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     |     You cannot call a method on a null-valued expression.

That's why I created a function that creates a array with an object for each value.
Of course, it can be improved, but we'll leave that for later...

function Get-Winget-Upgradables {
	$apps  = @()
	$start = $false

	# Remove unnecessary first lines
	winget upgrade --accept-source-agreements --include-unknown | foreach-object {
		if ( $psitem -match '^([-]+)$' ) {
			$start = $true
		}
		elseif ( $start -eq $true ) {
			$apps += $psitem
		}
	}

	# Remove the last line
	$apps = $apps[ 0..( $apps.length - 2 ) ]


	# Loop the array and create an object for any value
	$index = 0
	$apps.foreach({
		$pattern = "^(.+\u2026?)\s+([\u2026\.\w\+]+)\s+([\.\d]+)\s+([\.\d]+)\s+([\w]+)$"
		$apps[ $index ] = @{
			'name'      = ( $apps[ $index ] -replace $pattern, '$1' ) -replace '\s+$', ''
			'id'        = $apps[ $index ] -replace $pattern, '$2'
			'version'   = $apps[ $index ] -replace $pattern, '$3'
			'available' = $apps[ $index ] -replace $pattern, '$4'
			'source'    = $apps[ $index ] -replace $pattern, '$5'
		}

		$index += 1
	})

	return $apps
}

@houtianze
Copy link

@F4Jonatas Thanks for the feedback. I tried again and it works on my computer, my guess is that your system is set to some non-English language that shows a localized string of Name and that's why the function failed to locate the line and the index ran out of bound. Locating using ---... is a better option in this case.

@JoeyNice
Copy link

JoeyNice commented Dec 8, 2024

In addition to the language setting, it also makes a difference whether you use Powershell, Powershell ISE or Visual Studio Code.

This has a particular effect on the truncation of text. Sometimes it is a character string like  and sometimes it is a special character that looks like 3 dots (Unicode ellipsis [char]0x2026).

I encountered this problem in a similar project.

https://gist.github.com/nouseforname/a8b7ebcb9d0c05e380c7e3c81c300923?permalink_comment_id=5232687#gistcomment-5232687

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