DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
Developer Tools

How I Built a PowerShell Multi-File Search Tool for Code

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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

  1. Accepts a query and searches a selected folder recursively.
  2. Shows matching file paths in a scrollable ListBox.
  3. Loads the selected file into a read-only RichTextBox.
  4. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
PowerShell for Sysadmins: Workflow Automation Made Easy
  • 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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
$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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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-String exposes -Encoding; current PowerShell documentation covers BOM detection and newer options such as ansi in PowerShell 7.4 and later.
  • Binary and compressed files are not ordinary text. Filter them out unless you add format-specific parsers.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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 -List when 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.