How to Search a String in Multiple Files and Return the File Names in PowerShell
To find every file under a folder that contains a string, and print only the file names, run this in PowerShell:
Get-ChildItem -Path C:\Projects -Recurse -File | Select-String -Pattern "Timeout" -List | Select-Object -ExpandProperty Path
Get-ChildItem -Recurse -File lists every file in C:\Projects and its subfolders. Select-String reads each one and reports lines that match the pattern. -List stops at the first match per file, so each file appears once. Select-Object -ExpandProperty Path prints the full path as plain text instead of the match object.
Why Get-ChildItem does the recursion
Select-String has no -Recurse parameter. Its -Path parameter accepts wildcards, so Select-String -Path C:\Logs\*.log -Pattern "Error" searches one folder, but it will not descend into subfolders. For a recursive search, Get-ChildItem walks the tree and pipes the files in.
When one folder is enough, the shorter form works:
Select-String -Path C:\Logs\*.log -Pattern "Error" -List | Select-Object -ExpandProperty Path
Return only the file names
Select-String returns MatchInfo objects with Path, Filename, LineNumber and Line properties. Pick the property you want.
$files = Get-ChildItem -Path C:\Projects -Recurse -File # Full paths, one per file $files | Select-String -Pattern "Timeout" -List | Select-Object -ExpandProperty Path # File names without the folder $files | Select-String -Pattern "Timeout" -List | Select-Object -ExpandProperty Filename # Every matching line, then collapse to unique paths $files | Select-String -Pattern "Timeout" | Select-Object -ExpandProperty Path -Unique # Number of matching lines per file $files | Select-String -Pattern "Timeout" | Group-Object Path | Select-Object Name, Count
To save the list, pipe it to Set-Content:
$files | Select-String -Pattern "Timeout" -List | Select-Object -ExpandProperty Path | Set-Content C:\Temp\matches.txt
Useful variants
| Need | Add | Note |
|---|---|---|
| Literal text, not a regex | -SimpleMatch | -Pattern is a regular expression by default, so . and ( have special meaning |
| Case-sensitive match | -CaseSensitive | The default ignores case |
| Only some file types | -Include *.log,*.txt on Get-ChildItem | -Include needs -Recurse or a wildcard in the path |
| Skip a folder | Where-Object FullName -notlike "*\node_modules\*" before Select-String | -Exclude matches names, not full paths |
| Just true or false | -Quiet | One boolean per input; useful inside an if |
| Lines around each match | -Context 2,2 | Two lines before and two after |
| Files in another encoding | -Encoding UTF8 or Unicode | Set it when matches are missed in files that are not UTF-8 |
Two of these deserve a sentence. Searching for config.json without -SimpleMatch also matches configXjson, because . matches any character. And -NotMatch does not list files that lack the string; it lists lines that lack it. For files without the string, filter the other way:
$files | Where-Object { -not ($_ | Select-String -Pattern "Timeout" -Quiet) } | Select-Object -ExpandProperty FullName
Find a file by name
Finding a file by its name needs only Get-ChildItem; no content search is involved.
# Files whose name contains "config", anywhere under C:\Projects Get-ChildItem -Path C:\Projects -Recurse -File -Filter "*config*" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName # One exact name, searched from the drive root Get-ChildItem -Path C:\ -Recurse -File -Filter "web.config" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
-Filter is applied by the file system provider before any objects are created, so it is faster than -Include or a later Where-Object. -ErrorAction SilentlyContinue hides the access-denied errors that a scan from a drive root produces.
The two ideas combine. To search inside only the files whose names match, filter by name first and then read them:
Get-ChildItem -Path C:\Projects -Recurse -File -Filter "*.config" | Select-String -Pattern "connectionString" -List | Select-Object -ExpandProperty Path
On a large tree this is much faster than reading every file, because Select-String opens only the files that pass the filter.
Key Takeaways
- Recursion comes from
Get-ChildItem -Recurse, not fromSelect-String. Pipe the files in. -Listgives one result per file. ExpandPathfor full paths orFilenamefor names only.-Patternis a regex. Add-SimpleMatchwhen the text contains dots, brackets or parentheses.-Filterfinds files by name fastest. Use it beforeSelect-Stringto narrow the set of files to read.- Shell skills support interview preparation, but they are not the test. The coding rounds test patterns, which Grokking the Coding Interview teaches, and Grokking Data Structures for Coding Interviews covers the structures behind them. A mock interview shows how you explain a solution out loud.

GET YOUR FREE
Coding Questions Catalog

$99

$197

$72