What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
I built this Windows Forms utility after struggling to find code containing punctuation-heavy strings in a large project. It searches a folder recursively, lists files containing a match, previews the selected file, and highlights each occurrence. The result is a useful prototype and a practical lesson in PowerShell GUI programming—not a replacement for an indexed search engine.
What problem does it solve?
Windows Search can find documents and file names, but code-oriented searches are a different problem. Source files and configuration contain characters such as ., ?, *, +, (, [, {, |, ^, $ and . Those characters have special meanings in regular expressions, so a search for a pasted code fragment can produce surprising results unless the query is treated as literal text.
The original project, published by Brien Posey on October 22, 2024, searches a hard-coded C:Temp folder and provides a clickable result list and preview pane. See the original article for the narrative that inspired this implementation.
What the finished tool does
- Accepts a query and searches a selected folder recursively.
- Shows matching file paths in a scrollable
ListBox. - Loads the selected file into a read-only
RichTextBox. - Highlights occurrences of the original query in the preview.
The prototype uses a 1,920 × 1,080 form and enough list-box height for roughly 25 visible rows. That is a layout choice, not a general usability rule.
Recommended Free Tools
#1 Best Overall
- Book - powershell for sysadmins: workflow automation made easy
- Language: english
- Binding: paperback
Start with the search pipeline
The engine is simply recursive enumeration followed by content matching:
$root = 'C:Temp'
$query = 'Write-Host "Hello World!"'
Get-ChildItem -LiteralPath $root -Recurse -File |
Select-String -Pattern $query -SimpleMatch -List
Get-ChildItem -Recurse walks subdirectories. -File excludes directories. Select-String normally interprets its pattern as a regular expression; -SimpleMatch makes the query literal, and -List returns only the first match per file. The parameter behavior is documented in Microsoft’s Select-String reference.
Literal and regular-expression modes
Literal mode should be the default for pasted code and configuration text:
Select-String -Pattern $query -SimpleMatch -List
If the application deliberately offers regex searches, pass the user’s expression unchanged and validate it before scanning. If you need a regex-based pipeline but want literal semantics, escape only the pattern used by the scanner:
Rank #2
$safePattern = [regex]::Escape($query)
Get-ChildItem -LiteralPath $root -Recurse -File |
Select-String -Pattern $safePattern -List
[Regex]::Escape() protects regex metacharacters; it is not required merely because a query contains spaces. Keep the original query separately for display and highlighting.
Why PowerShell and Windows Forms?
- PowerShell is already available on Windows and can enumerate the file system directly.
Select-String, .NET classes and event handlers are built in.- A useful GUI can be produced without compiling a separate application.
- Administrators and developers can modify the script quickly.
The trade-off is that a serial file walk is slower than an indexed tool on very large repositories. Windows Forms is also verbose and Windows-oriented. PowerShell 7’s cross-platform engine does not make this GUI portable to Linux or macOS; the supplied interface depends on Windows Forms and System.Drawing. PowerShell’s platform differences are described in Microsoft’s Unix-support documentation.
Build the Windows Forms interface
Run this on Windows PowerShell 5.1 or PowerShell 7 with a desktop session:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'PowerShell Multi-File Search'
$form.Size = New-Object System.Drawing.Size(1200, 760)
$pathLabel = New-Object System.Windows.Forms.Label
$pathLabel.Text = 'Search folder:'
$pathLabel.Location = New-Object System.Drawing.Point(20, 20)
$pathLabel.AutoSize = $true
$searchPathBox = New-Object System.Windows.Forms.TextBox
$searchPathBox.Text = 'C:Temp'
$searchPathBox.Location = New-Object System.Drawing.Point(130, 17)
$searchPathBox.Size = New-Object System.Drawing.Size(700, 25)
$queryLabel = New-Object System.Windows.Forms.Label
$queryLabel.Text = 'Text to find:'
$queryLabel.Location = New-Object System.Drawing.Point(20, 58)
$queryLabel.AutoSize = $true
$queryBox = New-Object System.Windows.Forms.TextBox
$queryBox.Location = New-Object System.Drawing.Point(130, 55)
$queryBox.Size = New-Object System.Drawing.Size(700, 25)
$submitButton = New-Object System.Windows.Forms.Button
$submitButton.Text = 'Submit'
$submitButton.Location = New-Object System.Drawing.Point(850, 17)
$submitButton.Size = New-Object System.Drawing.Size(100, 30)
$exitButton = New-Object System.Windows.Forms.Button
$exitButton.Text = 'Exit'
$exitButton.Location = New-Object System.Drawing.Point(960, 17)
$exitButton.Size = New-Object System.Drawing.Size(100, 30)
$fileListBox = New-Object System.Windows.Forms.ListBox
$fileListBox.Location = New-Object System.Drawing.Point(20, 100)
$fileListBox.Size = New-Object System.Drawing.Size(540, 580)
$previewBox = New-Object System.Windows.Forms.RichTextBox
$previewBox.Location = New-Object System.Drawing.Point(580, 100)
$previewBox.Size = New-Object System.Drawing.Size(580, 580)
$previewBox.ReadOnly = $true
$previewBox.Font = New-Object System.Drawing.Font('Consolas', 10)
$form.Controls.AddRange(@($pathLabel, $searchPathBox, $queryLabel, $queryBox,
$submitButton, $exitButton, $fileListBox, $previewBox))
A ListBox is preferable to a plain text box for results because each path is selectable and the SelectedIndexChanged event identifies the file to preview.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesRank #3
Wire up a safer search button
$submitButton.Add_Click({
$userInput = $queryBox.Text
$searchPath = $searchPathBox.Text
if ([string]::IsNullOrWhiteSpace($userInput)) {
[System.Windows.Forms.MessageBox]::Show(
'Enter text to search for.', 'Search', 'OK', 'Warning')
return
}
if (-not (Test-Path -LiteralPath $searchPath -PathType Container)) {
[System.Windows.Forms.MessageBox]::Show(
'The search folder does not exist.', 'Search', 'OK', 'Error')
return
}
$fileListBox.BeginUpdate()
try {
$fileListBox.Items.Clear()
Get-ChildItem -LiteralPath $searchPath -Recurse -File `
-ErrorAction SilentlyContinue |
Select-String -Pattern $userInput -SimpleMatch -List `
-ErrorAction SilentlyContinue |
ForEach-Object { [void]$fileListBox.Items.Add($_.Path) }
}
finally {
$fileListBox.EndUpdate()
}
})
$exitButton.Add_Click({ $form.Close() })
-LiteralPath prevents wildcard interpretation in a user-supplied folder. -ErrorAction SilentlyContinue keeps routine permission failures from flooding the interface, but a production version should count or log skipped files rather than implying that an empty list proves there were no matches.
Restrict the files you scan
Scanning every file can hit executables, archives, images, databases and generated output. An extension allowlist is safer for a code tool:
$extensions = '.ps1','.psm1','.psd1','.cs','.js','.json','.xml','.txt'
Get-ChildItem -LiteralPath $searchPath -Recurse -File |
Where-Object { $_.Extension -in $extensions } |
Select-String -Pattern $userInput -SimpleMatch -List
You can also exclude directories such as .git, node_modules, bin and obj, impose a maximum file size, and cap the number of results.
Load the selected file without losing line breaks
$fileListBox.Add_SelectedIndexChanged({
if ($null -eq $fileListBox.SelectedItem) { return }
try {
$selectedPath = [string]$fileListBox.SelectedItem
$contents = Get-Content -LiteralPath $selectedPath -Raw -ErrorAction Stop
$previewBox.ReadOnly = $false
$previewBox.Text = $contents
$previewBox.ReadOnly = $true
}
catch {
$previewBox.Text = "Unable to read file:`r`n$($_.Exception.Message)"
}
})
Get-Content -Raw returns one string while preserving newline characters, unlike the normal line-array output. See Microsoft’s Get-Content documentation. It can use substantial memory for very large files, and it cannot correct an encoding mismatch. UTF-8, UTF-16, legacy ANSI and mixed-encoding repositories may require an explicit -Encoding choice.
Rank #4
Highlight the original query
Search escaping and display highlighting are separate concerns. If the user typed a.b, the scanner may use an escaped pattern, but the preview must search for the literal text a.b, not the backslash-containing escaped pattern.
$fileListBox.Add_SelectedIndexChanged({
if ($null -eq $fileListBox.SelectedItem) { return }
try {
$selectedPath = [string]$fileListBox.SelectedItem
$contents = Get-Content -LiteralPath $selectedPath -Raw -ErrorAction Stop
$previewBox.ReadOnly = $false
$previewBox.Text = $contents
$previewBox.SelectAll()
$previewBox.SelectionBackColor = $previewBox.BackColor
$previewBox.DeselectAll()
$searchText = $queryBox.Text
if (-not [string]::IsNullOrEmpty($searchText)) {
$index = 0
while (($found = $previewBox.Find(
$searchText,
$index,
[System.Windows.Forms.RichTextBoxFinds]::None
)) -ge 0) {
$previewBox.Select($found, $searchText.Length)
$previewBox.SelectionBackColor = [System.Drawing.Color]::Yellow
$index = $found + [Math]::Max($searchText.Length, 1)
}
}
$previewBox.DeselectAll()
$previewBox.ReadOnly = $true
}
catch {
$previewBox.ReadOnly = $false
$previewBox.Text = "Unable to read file:`r`n$($_.Exception.Message)"
$previewBox.ReadOnly = $true
}
})
This example performs literal highlighting. If the search supports case-sensitive matching, regex matching or multiline expressions, configure RichTextBox.Find() and the scanner consistently. Clear old formatting before loading a new file. Formatting thousands of matches can itself make the control slow.
Encoding, permissions and changing files
- A file can be deleted, renamed, locked or replaced after enumeration; preview reads must catch exceptions.
- Recursive searches can encounter access-denied paths. Microsoft documents this caveat for
Select-String. - Incorrect decoding can cause missed matches or garbled text.
Select-Stringexposes-Encoding; current PowerShell documentation covers BOM detection and newer options such asansiin PowerShell 7.4 and later. - Binary and compressed files are not ordinary text. Filter them out unless you add format-specific parsers.
Performance limits and practical upgrades
The synchronous button handler runs the directory walk on the GUI thread. A large repository, network share, antivirus scan or very large file can make the window appear frozen. A more serious version should move scanning to a background runspace or asynchronous task and provide a progress indicator and Cancel button.
- Use extension and directory filters.
- Keep
-Listwhen you only need file paths. - Add a maximum file size and result limit.
- Report skipped files and access errors.
- Debounce repeated submissions.
- Add line numbers, syntax coloring, an external-editor button and search history.
For repeated searches across millions of files, an index is usually a better architecture than walking and decoding every candidate on every click.
Best Value
Build or use an existing search tool?
| Option | Best for | Trade-off |
|---|---|---|
| PowerShell GUI | Learning, automation and custom workflows | Requires maintenance; synchronous prototype does not scale indefinitely |
| ripgrep | Fast source-code searches from a terminal | No built-in Windows Forms preview interface |
| Everything | Very fast Windows file-name discovery | Less programmable than a custom script |
| Agent Ransack | Ready-made Windows content-search GUI | Less customizable than PowerShell |
| FileLocator Pro | Professional filtering and search workflows | Commercial licensing; verify current pricing and editions |
PowerShell itself is free and documented at Microsoft Learn. Choose the script when customization is the point; choose a dedicated utility when you need indexing, polished navigation or large-scale performance immediately.
What this prototype is—and is not
This is a functional Windows teaching project for small, text-oriented folders. It demonstrates recursive enumeration, literal versus regex matching, Windows Forms events, newline-preserving previews and RichTextBox highlighting. It is not an indexed search engine, a syntax-aware code browser or a guaranteed search of every file. Unreadable, binary, changing and incorrectly decoded files can all affect results.
Frequently Asked Questions
Does Select-String search literal text by default?
No. It treats the pattern as a regular expression unless you use -SimpleMatch or escape literal input with [Regex]::Escape().
Why use Get-Content -Raw?
It returns the file as one string while preserving newline characters, which lets the preview retain the file’s line structure.
Will this Windows Forms tool run unchanged on Linux or macOS?
No. The search concepts are portable, but the supplied GUI depends on Windows Forms and System.Drawing and is Windows-oriented.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




