I get all the packages installed on my PC using Get-AppxPackage
and I'm trying to find all the matches in that with N lines before and after using Select-String
.
However, the select string is only showing a matches as single line and it's not showing all the matches either. This only happens when I pipe the output from Get-AppxPackage
and not if I write it to a file and then do cat <filename> | select-string ...
.
As you can see in the example below the two results of using pipe and cat
. I'm interested in results like from cat
i.e. detailed info about the app.
So what am I doing wrong here? Why is the output different?
Example (everyone should have MS Edge so I'll use that as an example) :
PS > Get-AppxPackage | Select-String -pattern 'edge' -context 3, 3 -allmatches
Microsoft.Windows.StartMenuExperienceHost_10.0.18362.329_neutral_neutral_cw5n1h2txyewy
Microsoft.Windows.Cortana_1.13.0.18362_neutral_neutral_cw5n1h2txyewy
Microsoft.AAD.BrokerPlugin_1000.18362.329.0_neutral_neutral_cw5n1h2txyewy
> Microsoft.MicrosoftEdge_44.18362.329.0_neutral__8wekyb3d8bbwe
Microsoft.Windows.CloudExperienceHost_10.0.18362.329_neutral_neutral_cw5n1h2txyewy
Microsoft.Windows.ContentDeliveryManager_10.0.18362.329_neutral_neutral_cw5n1h2txyewy
Windows.CBSPreview_10.0.18362.329_neutral_neutral_cw5n1h2txyewy
Microsoft.Windows.Apprep.ChxApp_1000.18362.329.0_neutral_neutral_cw5n1h2txyewy
Microsoft.Win32WebViewHost_10.0.18362.329_neutral_neutral_cw5n1h2txyewy
Microsoft.PPIProjection_10.0.18362.329_neutral_neutral_cw5n1h2txyewy
> Microsoft.MicrosoftEdgeDevToolsClient_1000.18362.329.0_neutral_neutral_8wekyb3d8bbwe
Microsoft.LockApp_10.0.18362.329_neutral__cw5n1h2txyewy
> Microsoft.EdgeDevtoolsPlugin_10.0.18362.329_neutral_neutral_cw5n1h2txyewy
Microsoft.ECApp_10.0.18362.329_neutral__8wekyb3d8bbwe
Microsoft.CredDialogHost_10.0.18362.329_neutral__cw5n1h2txyewy
Microsoft.BioEnrollment_10.0.18362.329_neutral__cw5n1h2txyewy
PS > cat .\appx-packages.txt | select-string -pattern 'edge' -context 3, 3 -allmatches
SignatureKind : System
Status : Ok
> Name : Microsoft.MicrosoftEdge
Publisher : CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US
Architecture : Neutral
ResourceId :
Version : 44.18362.329.0
> PackageFullName : Microsoft.MicrosoftEdge_44.18362.329.0_neutral__8wekyb3d8bbwe
> InstallLocation : C:\Windows\SystemApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe
IsFramework : False
> PackageFamilyName : Microsoft.MicrosoftEdge_8wekyb3d8bbwe
PublisherId : 8wekyb3d8bbwe
IsResourcePackage : False
IsBundle : False
SignatureKind : System
Status : Ok
> Name : Microsoft.MicrosoftEdgeDevToolsClient
Publisher : CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US
Architecture : Neutral
ResourceId : neutral
Version : 1000.18362.329.0
> PackageFullName : Microsoft.MicrosoftEdgeDevToolsClient_1000.18362.329.0_neutral_neutral_8wekyb3d8bbwe
> InstallLocation : C:\Windows\SystemApps\Microsoft.MicrosoftEdgeDevToolsClient_8wekyb3d8bbwe
IsFramework : False
> PackageFamilyName : Microsoft.MicrosoftEdgeDevToolsClient_8wekyb3d8bbwe
PublisherId : 8wekyb3d8bbwe
IsResourcePackage : False
IsBundle : False
SignatureKind : System
Status : Ok
> Name : Microsoft.EdgeDevtoolsPlugin
Publisher : CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US
Architecture : Neutral
ResourceId : neutral
Version : 10.0.18362.329
> PackageFullName : Microsoft.EdgeDevtoolsPlugin_10.0.18362.329_neutral_neutral_cw5n1h2txyewy
> InstallLocation : C:\Windows\SystemApps\Microsoft.EdgeDevtoolsPlugin_cw5n1h2txyewy
IsFramework : False
> PackageFamilyName : Microsoft.EdgeDevtoolsPlugin_cw5n1h2txyewy
PublisherId : cw5n1h2txyewy
IsResourcePackage : False
IsBundle : False
tl;dr
As of PowerShell v7.3, to make Select-String
search non-string input by the same rich string representation you'd see in the console (terminal), you must pipe the input to oss
first:
Get-AppxPackage | oss | Select-String -Pattern 'edge' -Context 3, 3
Note:
This intermediate step should not be necessary, as discussed in GitHub issue #10726.
In fact it already isn't necessary when you're piping non-string input to an external program instead, say findstr.exe
, because PowerShell then implicitly uses the rich string representation, because it has to send the input as (meaningful) strings.[1]
As an aside: Select-String
is convenient for searching through lines of text and - with oss
- for searching the formatted string representations of objects, without having to know or care about their specific structure.
However, if you do know the structure, using OO techniques via different cmdlets, such as Where-Object
and Select-Object
is more robust. For instance, the following filters packages by their .Name
property and then selects only the properties of interest:
# Note: Get-AppXPackage *edge* would obviate the need for Where-Object
Get-AppXPackage |
Where-Object Name -like *edge* |
Select-Object Name, Version
Select-String
, when given input other than strings, uses simple .ToString()
stringification[2] on each input object before looking for the given pattern.
In your case, the [Microsoft.Windows.Appx.PackageManager.Commands.AppxPackage]
instances output by Get-AppXPackage
stringify to the full package names (e.g., Microsoft.MicrosoftEdge_44.18362.387.0_neutral__8wekyb3d8bbwe
), which explains your output.
In order to make Select-String
search the for-display string representations of objects - as they would print to the console and as they would appear in a file saved to with >
/ Out-File
(cat
is Out-File
's built-in alias on Windows) - you must, surprisingly,
use Out-String -Stream
as an intermediate pipeline segment; since PowerShell v5, if memory serves, you can use built-in wrapper function oss
for brevity:
# oss is a built-in wrapper function for Out-String -Stream
Get-AppxPackage | oss | Select-String -Pattern 'edge' -Context 3, 3
Out-String
uses PowerShell's formatting system to produce human-friendly display representations of the input objects, the same way that default console output, the Format-*
cmdlets, and >
/ Out-File
do.
-Stream
causes the output lines to be sent through the pipeline one by one, so that Select-String
can match individual lines.
Given that the solution is both non-obvious and cumbersome, it would be nice if Select-String
directly supported this behavior, ideally by default, but at least on an opt-in basis via a switch parameter - see feature request #10726 on GitHub - up-vote the proposal there if you agree.
[1] As of v7.3, PowerShell only "speaks text" when communicating with external programs, so it has to create a string representation of non-string objects when passing data to them: see this answer. While it makes sense to default to the for-display string representation of such objects, note that this representation isn't meant for programmatic processing; for the latter, it's best to explicitly output a structured text format, such as JSON via ConvertTo-Json
.
[2] More accurately, .psobject.ToString()
is called, either as-is, or - if the object's ToString
method supports an IFormatProvider
-typed argument - as .psobject.ToString([cultureinfo]::InvariantCulture)
so as to obtain a culture-invariant representation - see this answer for more information.