Skip to content

Instantly share code, notes, and snippets.

@marzme
Last active June 23, 2024 23:53
Show Gist options
  • Save marzme/34fe1a7a003b60847bb26fbff865bf51 to your computer and use it in GitHub Desktop.
Save marzme/34fe1a7a003b60847bb26fbff865bf51 to your computer and use it in GitHub Desktop.
A PowerShell function that converts the text output from winget into PowerShell objects.
Function Convert-WingetTextToObject {
<#
.SYNOPSIS
Converts the text output from winget commands into custom PowerShell objects.
.DESCRIPTION
Converts the text output from winget commands into custom PowerShell objects.
.PARAMETER WingetRawText
The text output of a winget command.
.EXAMPLE
PS C:> Convert-WingetTextToObject (winget list) | Sort Id | ft -AutoSize
.EXAMPLE
PS C:> winget upgrade | cvwg | Sort Name | ft -AutoSize
.EXAMPLE
PS C:> winget upgrade | cvwg | Where {$_.Id -notlike "Microsoft*"} | foreach {winget upgrade $_.Id}
.EXAMPLE
PS C:> winget search adobe | cvwg | Where {$_.Name -like "*Reader*"} | Sort Name | ft -AutoSize
.INPUTS
System.String
.OUTPUTS
PSCustomObject
.NOTES
Additional information about the function goes here.
#>
[CmdletBinding()]
Param(
# WingetRawText parameter is actually mandatory, but it isn't set as such because it has to be able to accept empty input as sometimes there's blank lines in the output from winget
[Parameter(Position=0, ValueFromPipeline=$true)]
[String[]]
$WingetRawText
)#PARAM
BEGIN {
# Initialise WingetAccumulatedText
$WingetAccumulatedText = @()
}# BEGIN
PROCESS {
# If the current row isn't empty then add it to the accumulated text
if ($WingetRawText) {$WingetAccumulatedText += $WingetRawText}
}# PROCESS
END {
TRY {
# Check if WingetAccumulatedText is empty
if (!$WingetAccumulatedText) {
Throw "No input received."
} else {
# If there are any packages that require explicit targeting for upgrade, remove that text and the subsequent text from the WingetAccumulatedText string
$ExplicitTargetTextLocation = $WingetAccumulatedText | Where {$_ -like "*require explicit targeting for upgrade*"} | foreach {$WingetAccumulatedText.IndexOf($_)}
if ($ExplicitTargetTextLocation) {
$ExplicitTargetTextCutoff = $WingetAccumulatedText.Length - $ExplicitTargetTextLocation + 1
$WingetAccumulatedText = $WingetAccumulatedText[0..($WingetAccumulatedText.Length - $ExplicitTargetTextCutoff)]
}# if ($ExplicitTargetTextLocation)...
# Find the header row that contains the column titles, by finding the row ABOVE the row of all dashes (it is assumed this will always be the header row)
$RowOfDashes = $WingetAccumulatedText | Where {$_ -match "^-+$"}
if ($RowOfDashes) {$HeaderRowIndex = ($WingetAccumulatedText.IndexOf($RowOfDashes))[0] - 1} else {Throw "Couldn't find header row."}
$HeaderRowText = $WingetAccumulatedText[$HeaderRowIndex]
$DataRowsText = $WingetAccumulatedText[($HeaderRowIndex + 2)..($WingetAccumulatedText.Length - 1)]
# Get an array of the column titles from the header row of the table produced by Winget
$ColumnNames = $HeaderRowText -Split '\s+'
# Find location of the first character of each column using the titles in the header row, and create a unique variable for each
$i = 0
$ColumnNames | foreach {
$i++
New-Variable -Name "Col$($i)CharLocation" -Value $HeaderRowText.IndexOf("$_")
}# $ColumnNames | foreach...
# Get an array of the character location variables that were created
$ColumnCharLocationVars = Get-Variable -Name "Col*CharLocation"
# So that the data values in the table can be accurately extracted, clean up and format the data by:
# * Removing commonly occurring special characters that result from encoding translation issues (eg "ÔǪ" for an ellipses, and "┬«" for a Registered Trademark symbol)
# * Inserting extra spaces between each of the columns, starting with the last column (for ease of converting to object by ConvertFrom-String CMDlet using spaces as delimiters).
# * Adding the letter "v" (temporarily) to the start of the values in each column (except the first column) to prevent some decimal values being turned into DateTime properties (eg if a version number is formatted similar to a date, like '10.08.26').
# Then convert the cleaned text values to a PS custom object
$WingetObjects = $DataRowsText.Replace("ÔǪ"," ").Replace("┬«","r") | Where {$_ -notlike "*upgrades available*"} | foreach {
$CurrentRow = $_
$ColumnCharLocationVars | Sort Name -Descending | Select -First ($ColumnCharLocationVars.Count - 1) | foreach {$CurrentRow = $CurrentRow.Insert($_.Value,' v')}
$CurrentRow
} | ConvertFrom-String -Delimiter '\s\s\s+' -PropertyNames $ColumnNames
# In the new custom object, remove the temporary letter "v" that was added to the start of the values in each column (except the first column)
foreach ($CurrentObject in $WingetObjects) {
$ColumnNames | Select -Last ($ColumnNames.Count - 1) | foreach {
# If it's a single character, then set the value to blank, otherwise, trim the v from the start of the string
if ($CurrentObject.$_.length -eq 1) {$CurrentObject.$_ = ""}
else {[string]$CurrentObject.$_ = $CurrentObject.$_.TrimStart('v')}
}# $ColumnNames | Select...
}# foreach ($CurrentObject...
# Output the resulting objects
$WingetObjects
}# else
}# TRY
CATCH {
Throw $Error[0].Exception.Message
}# CATCH
}# END
}# Function Convert-WingetTextToObject...
New-Alias -Name cvwg -Value Convert-WingetTextToObject
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment