To complement the existing, helpful answers:
I would like to see a powershell solution similar to the Bash sort -k <column-number>
, to sort on any column.
I fail to see why this obvious function is not available in powershell?
The sort
utility does not sort by columns with -k
(--key
); it sorts by fields, with any non-empty run of whitespace acting as the field separator by default.
Given that a field-based solution isn't possible here - the fields have fixed width, so there's no separator (-t
, --field-separator
) you can specify - you'd have to use -k 1.40
to achieve column-based sorting, which is (a) far from obvious and (b) is the equivalent of passing { $_.substring(39) }
to Sort-Object
's -Property
parameter, as in js2010's answer.
winget list | Sort-Object -Property Id
While -Property Id
would indeed be wonderful if it worked, it cannot be expected to work with the text representations that the external program winget.exe
outputs: what PowerShell then sees in the pipeline are strings, about whose content nothing is known, so they can't be expected to have an .Id
property.
Should the functionality provided by winget.exe
ever be exposed in a PowerShell-native way,[1] i.e. via cmdlets, they would indeed produce (non-string) objects with properties that would allow you to use Sort-Object -Property Id
.
Dealing with winget.exe
directly comes with the following challenges, owing to its nonstandard behavior (see also the bottom section):
It doesn't respect the current console's code page and instead invariably outputs UTF-8-encoded output.
- To compensate for that,
[Console]::OutputEncoding
must (temporarily) be set to [System.Text.UTF8Encoding]::new()
It doesn't modify its progress-display behavior based on whether its stdout stream is connected directly to a console (terminal) or not; that is, it should suppress progress information when its output is being captured or redirected, but it currently isn't.
- To compensate for that, the initial output lines that are the result of
winget.exe
's progress display must be filtered out.
Thus, an adapted version of js2010's answer would look like this:
# Make PowerShell interpret winget.exe's output as UTF-8.
# You may want to restore the original [Console]::OutputEncoding afterwards.
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
(winget list) -match '^\p{L}' | # filter out progress-display and header-separator lines
Select-Object -Skip 1 | # skip the header line
Sort-Object { $_.Substring(39) }
Parsing winget.exe list
output into objects:
The textual output from winget.exe list
reveals an inherent limitation that PowerShell-native commands with their separation of data output from its presentation do not suffer from: truncating property values with …
represents omission of information that cannot be recovered.
Thus, the following solution is limited by whatever information is present in winget.exe
's textual output.
Assuming that helper function ConvertFrom-FixedColumnTable
(source code below) is already defined, you can use it to transform the fixed-with-column textual output into objects ([pscustomobject]
instances) whose properties correspond to the table's columns, which then allows you to sort by properties (columns), and generally enables OOP processing of the output.
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
(winget list) -match '^(\p{L}|-)' | # filter out progress-display lines
ConvertFrom-FixedColumnTable | # parse output into objects
Sort-Object Id | # sort by the ID property (column)
Format-Table # display the objects in tabular format
ConvertFrom-FixedColumnTable
source code:
# Note:
# * Accepts input only via the pipeline, either line by line,
# or as a single, multi-line string.
# * The input is assumed to have a header line whose column names
# mark the start of each field
# * Column names are assumed to be *single words* (must not contain spaces).
# * The header line is assumed to be followed by a separator line
# (its format doesn't matter).
function ConvertFrom-FixedColumnTable {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)] [string] $InputObject
)
begin {
Set-StrictMode -Version 1
$lineNdx = 0
}
process {
$lines =
if ($InputObject.Contains("`n")) { $InputObject.TrimEnd("`r", "`n") -split '\r?\n' }
else { $InputObject }
foreach ($line in $lines) {
++$lineNdx
if ($lineNdx -eq 1) {
# header line
$headerLine = $line
}
elseif ($lineNdx -eq 2) {
# separator line
# Get the indices where the fields start.
$fieldStartIndices = [regex]::Matches($headerLine, '\b\S').Index
# Calculate the field lengths.
$fieldLengths = foreach ($i in 1..($fieldStartIndices.Count-1)) {
$fieldStartIndices[$i] - $fieldStartIndices[$i - 1] - 1
}
# Get the column names
$colNames = foreach ($i in 0..($fieldStartIndices.Count-1)) {
if ($i -eq $fieldStartIndices.Count-1) {
$headerLine.Substring($fieldStartIndices[$i]).Trim()
} else {
$headerLine.Substring($fieldStartIndices[$i], $fieldLengths[$i]).Trim()
}
}
}
else {
# data line
$oht = [ordered] @{} # ordered helper hashtable for object constructions.
$i = 0
foreach ($colName in $colNames) {
$oht[$colName] =
if ($fieldStartIndices[$i] -lt $line.Length) {
if ($fieldLengths[$i] -and $fieldStartIndices[$i] + $fieldLengths[$i] -le $line.Length) {
$line.Substring($fieldStartIndices[$i], $fieldLengths[$i]).Trim()
}
else {
$line.Substring($fieldStartIndices[$i]).Trim()
}
}
++$i
}
# Convert the helper hashable to an object and output it.
[pscustomobject] $oht
}
}
}
}
Optional reading: potential winget.exe
improvements:
The fact that winget.exe
doesn't honor the console code page (as reported by chcp
/ [Console]::OutputEncoding
) and instead invariably outputs UTF-8 is problematic, but somewhat justifiable nowadays, given that UTF-8 has become the most widely used character encoding, across all platforms, and is capable of encoding all Unicode characters, whereas the legacy Windows code pages are limited to 256 characters. Other utilities have made a similar decision, notably node.exe
, the NodeJS CLI (Python is non-standard too, but has chosen the legacy ANSI code page as its default, though can be configured to use UTF-8).
In fact, it is the use of UTF-8 that enables use of …
(the horizontal ellipsis character U+2026
) in the output, which is a space-efficient way to indicate omission of data (the ASCII alternative would be to use ...
, i.e. three (.
) characters.
winget.exe
's encoding behavior isn't a problem if you've configured your (Windows 10 and above) system to use UTF-8 system-wide, which, however, has far-reaching consequences - see this answer.
Now that PowerShell (Core) itself consistently defaults to UTF-8, you could argue that even if the system as a whole doesn't use UTF-8 PowerShell console windows should - see GitHub issue #7233.
winget.exe
should test whether its stdout stream is connected to a console (terminal) and only then output progress information, so as to avoid polluting its stdout data output.
The currently unavoidable truncation of column values that exceed the fixed column width could be avoided with an opt-in mechanism to provide output in a structured text format that is suitable for programmatic processing, such as CSV, similar to what the (now deprecated) wmic.exe
utility has always offered with its /format
option.
- As noted, if in the future PowerShell cmdlets that provide the same functionality as
winget.exe
are made available, the problem wouldn't even arise there, given PowerShell's fundamental separation between (strongly typed) data and its - selectable - for-display representation.
[1] WinGet for PackageManagement is an example of a third-party module aimed at that.
Sort-Object
. What's not being understood is thatwinget list
does not return objects, it returns text. You can reorganize the output to return objects. – Ellen