Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
April 18, 2025

Creating Select-String Tools

In the previous article, I demonstrated how to take advantage of the Select-String cmdlet. This is the PowerShell version of grep. However, that doesn't mean you have to use it. I am an advocate of the right tool for the job. You have to determine what you are trying to accomplish. Do you want to do something else with the results in PowerShell? Do you merely need a report? How many files are you searching and is performance a concern?

A reader sent me an email recommending a command-line grep replacement called ripgrep. This is a cross-platform tool that is supposedly very fast. You can install it in Windows using Winget

winget install BurntSushi.ripgrep.MSVC

Or Chocolatey

choco install ripgrep

It was already installed in my WSL Ubuntu instance.

ripgrep
figure 1

The tool has many more features than Select-String. My intention isn't to teach you about ripgrep but rather to re-enforce the idea of the right tool for the job. I think a major part of that decision is knowing what you want to do with the results or what you want to accomplish.

You can learn more at the project's GitHub repository.

For now, let's return to Select-String and see how we can create some tools around it.

We already know that command isn't that difficult to invoke.

dir *.md | Select-String 'invoke-c\w+'
Sample Select-String output
figure 2

The output is where things can get confusing. Select-String writes a structured object to the pipeline. It is easy to forget about objects when parsing text. Of course, the best way to learn more is to use the Get-Member cmdlet.

PS D:\PSBehind\manuscript> dir *.md | Select-String 'invoke-c\w+' | Get-Member

   TypeName: Microsoft.PowerShell.Commands.MatchInfo

Name               MemberType Definition
----               ---------- ----------
Equals             Method     bool Equals(System.Object obj)
GetHashCode        Method     int GetHashCode()
GetType            Method     type GetType()
RelativePath       Method     string RelativePath(string directory)
ToEmphasizedString Method     string ToEmphasizedString(string directory)
ToString           Method     string ToString(), string ToString(string directory)
Context            Property   Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename           Property   string Filename {get;}
IgnoreCase         Property   bool IgnoreCase {get;set;}
Line               Property   string Line {get;set;}
LineNumber         Property   ulong LineNumber {get;set;}
Matches            Property   System.Text.RegularExpressions.Match[] Matches {get;set;}
Path               Property   string Path {get;set;}
Pattern            Property   string Pattern {get;set;}

Then I like to look at a sample object.

PS D:\PSBehind\manuscript> dir *.md | Select-String 'invoke-c\w+' | Select-Object -First 1 -Property *

IgnoreCase : True
LineNumber : 457
Line       :                 $os = Invoke-Command -ScriptBlock {
Filename   : data-files-as-script-files.md
Path       : D:\PSBehind\manuscript\data-files-as-script-files.md
Pattern    : invoke-c\w+
Context    :
Matches    : {0}

Now that I know the properties and values, I can write a PowerShell command to present the information in a more readable format. I can use the Format-Table cmdlet to display the information in a table format.

dir *.md | Select-String 'invoke-c\w+' |
Format-Table -GroupBy FileName -Property LineNumber,@{Name="Line";Expression={$_.Line.Trim()}}
Matching line information
figure 3

I am trimming white spaces from the matching line to make a nicer looking format. Or maybe I need to see how many matches are in each file. PowerShell makes it easy to manipulate the output and create something meaningful.

PS D:\PSBehind\manuscript> dir *.md | Select-String 'invoke-c\w+'|
Group-Object Path -NoElement | Select-Object @{Name="Path";Expression={$_.name}},Count

Path                                                     Count
----                                                     -----
D:\PSBehind\manuscript\data-files-as-script-files.md         5
D:\PSBehind\manuscript\extending-type.md                     2
D:\PSBehind\manuscript\profiles.md                           5
D:\PSBehind\manuscript\writing-better-powershell-code.md     4

Or maybe I want to copy the matching files.

PS D:\PSBehind\manuscript> Select-String -pattern 'invoke-c\w+' -path *.md -List |
Copy-Item -Destination d:\temp -PassThru

    Directory: D:\temp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           3/19/2025  6:05 PM          21450 data-files-as-script-files.md
-a---           3/19/2025  6:05 PM          41772 extending-type.md
-a---           3/19/2025  6:05 PM          30503 profiles.md
-a---           2/28/2025  3:23 PM          23645 writing-better-powershell-code.md

Discovering objects makes all of this possible with very little effort.

Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.