Skip to content

Searching Files

Note

Master Get-ChildItem and Select-String to find files by name, properties, or content efficiently.

Overview

Searching for files is one of the most common PowerShell tasks. Whether you need to find files by name, extension, date, size, or content, PowerShell provides powerful tools that go far beyond the basic Windows search. Get-ChildItem searches by file properties, while Select-String searches file contents.

Basic Syntax

# Get-ChildItem (alias: gci, ls, dir)
Get-ChildItem -Path C:\Folder -Filter *.txt -Recurse

# Select-String (grep for PowerShell)
Select-String -Path C:\Folder\*.txt -Pattern "error"

Key Points

  • Get-ChildItem finds files by properties (name, date, size, attributes)
  • Select-String searches inside file contents
  • Use -Recurse to search subfolders
  • Use -Filter for better performance than -Include
  • Combine cmdlets with pipeline for powerful filtering

Finding Files by Name

Search by Extension

# All .txt files in current directory
Get-ChildItem -Filter *.txt

# All .log files including subfolders
Get-ChildItem -Path C:\Logs -Filter *.log -Recurse

# Multiple extensions
Get-ChildItem -Path C:\Temp -Include *.txt,*.log,*.csv -Recurse

Search by Name Pattern

# Files starting with "report"
Get-ChildItem -Path C:\Reports -Filter report*

# Files containing "backup" anywhere in name
Get-ChildItem -Path C:\Data -Filter *backup* -Recurse

# Exact file name
Get-ChildItem -Path C:\Scripts -Filter Install.ps1 -Recurse
# Find files with exact case match
Get-ChildItem -Path C:\Temp | Where-Object {
    $_.Name -clike "*ERROR*"  # -clike is case-sensitive
}

Finding Files by Properties

Search by Date

# Files modified in last 7 days
$cutoffDate = (Get-Date).AddDays(-7)
Get-ChildItem -Path C:\Data -Recurse | Where-Object {
    $_.LastWriteTime -gt $cutoffDate
}

# Files created today
Get-ChildItem -Path C:\Temp -Recurse | Where-Object {
    $_.CreationTime.Date -eq (Get-Date).Date
}

# Files older than 30 days
$oldDate = (Get-Date).AddDays(-30)
Get-ChildItem -Path C:\Archive -Recurse | Where-Object {
    $_.LastWriteTime -lt $oldDate
}

# Files modified between two dates
$startDate = Get-Date "2025-01-01"
$endDate = Get-Date "2025-12-31"
Get-ChildItem -Path C:\Reports -Recurse | Where-Object {
    $_.LastWriteTime -ge $startDate -and $_.LastWriteTime -le $endDate
}

Search by Size

# Files larger than 100 MB
Get-ChildItem -Path C:\Downloads -Recurse | Where-Object {
    $_.Length -gt 100MB
}

# Files between 1 MB and 10 MB
Get-ChildItem -Path C:\Data -Recurse | Where-Object {
    $_.Length -ge 1MB -and $_.Length -le 10MB
}

# Empty files (0 bytes)
Get-ChildItem -Path C:\Temp -Recurse -File | Where-Object {
    $_.Length -eq 0
}

# Show files sorted by size
Get-ChildItem -Path C:\Logs -Recurse -File |
    Sort-Object Length -Descending |
    Select-Object Name, @{Name="SizeMB";Expression={[math]::Round($_.Length/1MB,2)}}

Search by Attributes

# Hidden files
Get-ChildItem -Path C:\Windows -Hidden -Force

# Read-only files
Get-ChildItem -Path C:\Data -Recurse | Where-Object {
    $_.IsReadOnly
}

# System files
Get-ChildItem -Path C:\Windows -System -Force

# Files that are NOT read-only
Get-ChildItem -Path C:\Temp -Recurse | Where-Object {
    -not $_.IsReadOnly
}

# Archived files
Get-ChildItem -Path C:\Backup -Recurse | Where-Object {
    $_.Attributes -match "Archive"
}

Searching File Contents

# Find files containing "error"
Select-String -Path C:\Logs\*.log -Pattern "error"

# Case-sensitive search
Select-String -Path C:\Logs\*.log -Pattern "ERROR" -CaseSensitive

# Search recursively
Select-String -Path C:\Logs\*.log -Pattern "error" -Recurse

# Show only file names (not matching lines)
Select-String -Path C:\Logs\*.log -Pattern "error" -List

Output:

C:\Logs\app.log:15:ERROR: Failed to connect
C:\Logs\app.log:42:ERROR: Timeout exceeded
C:\Logs\system.log:8:Error occurred during startup

# Multiple patterns (OR logic)
Select-String -Path C:\Logs\*.log -Pattern "error","warning","critical"

# Get context lines (before and after match)
Select-String -Path C:\Logs\app.log -Pattern "error" -Context 2,3
# Shows 2 lines before and 3 lines after each match

# Search specific file types
Get-ChildItem -Path C:\Code -Include *.ps1,*.psm1 -Recurse |
    Select-String -Pattern "function Get-"

# Count matches per file
Get-ChildItem -Path C:\Logs -Filter *.log | ForEach-Object {
    $matches = Select-String -Path $_.FullName -Pattern "error" -AllMatches
    [PSCustomObject]@{
        File = $_.Name
        ErrorCount = $matches.Count
    }
}
# Find IP addresses in files
Select-String -Path C:\Logs\*.log -Pattern "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"

# Find email addresses
Select-String -Path C:\Data\*.txt -Pattern "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"

# Find dates in YYYY-MM-DD format
Select-String -Path C:\Reports\*.txt -Pattern "\d{4}-\d{2}-\d{2}"

# Find lines starting with "ERROR:"
Select-String -Path C:\Logs\*.log -Pattern "^ERROR:"

# Find lines ending with "failed"
Select-String -Path C:\Logs\*.log -Pattern "failed$"

Excluding Folders and Files

Skip Specific Folders

# Exclude system folders
Get-ChildItem -Path C:\Code -Recurse | Where-Object {
    $_.FullName -notmatch "\\node_modules\\|\\\.git\\|\\bin\\|\\obj\\"
}

# Exclude multiple folder patterns
Get-ChildItem -Path C:\Projects -Recurse -File | Where-Object {
    $_.DirectoryName -notlike "*\node_modules\*" -and
    $_.DirectoryName -notlike "*\.git\*" -and
    $_.DirectoryName -notlike "*\bin\*"
}

Skip Specific File Types

# All files except .tmp and .bak
Get-ChildItem -Path C:\Data -Recurse | Where-Object {
    $_.Extension -notin @(".tmp", ".bak")
}

# Exclude files matching pattern
Get-ChildItem -Path C:\Logs -Recurse | Where-Object {
    $_.Name -notlike "*.old" -and $_.Name -notlike "*.backup"
}

Real-World Examples

Example: Find Large Old Log Files

Scenario: Clean up log files larger than 50MB that are older than 30 days

$path = "C:\Logs"
$sizeLimitMB = 50
$daysOld = 30
$cutoffDate = (Get-Date).AddDays(-$daysOld)

$oldLargeLogs = Get-ChildItem -Path $path -Filter *.log -Recurse | Where-Object {
$_.Length -gt ($sizeLimitMB * 1MB) -and
$_.LastWriteTime -lt $cutoffDate
}

# Display findings
$oldLargeLogs | Select-Object Name,
@{Name="SizeMB";Expression={[math]::Round($_.Length/1MB,2)}},
LastWriteTime,
FullName | Format-Table -AutoSize

# Optional: Delete after review
# $oldLargeLogs | Remove-Item -Force

Example: Search Code for Deprecated Functions

Scenario: Find all PowerShell scripts using a deprecated function

$searchPath = "C:\Scripts"
$deprecatedFunction = "Get-WmiObject"
$replacement = "Get-CimInstance"

$results = Get-ChildItem -Path $searchPath -Include *.ps1,*.psm1 -Recurse |
Select-String -Pattern $deprecatedFunction

# Display results grouped by file
$results | Group-Object Path | ForEach-Object {
Write-Output "`nFile: $($_.Name)"
Write-Output "Occurrences: $($_.Count)"
$_.Group | ForEach-Object {
    Write-Output "  Line $($_.LineNumber): $($_.Line.Trim())"
}
}

Write-Output "`nRecommendation: Replace '$deprecatedFunction' with '$replacement'"

Example: Find Duplicate Files by Name

Scenario: Identify files with the same name in different folders

$searchPath = "C:\Documents"

$duplicates = Get-ChildItem -Path $searchPath -Recurse -File |
Group-Object Name |
Where-Object {$_.Count -gt 1}

foreach ($dup in $duplicates) {
Write-Output "`nDuplicate file: $($dup.Name)"
Write-Output "Found in $($dup.Count) locations:"
$dup.Group | ForEach-Object {
    Write-Output "  - $($_.FullName) (Modified: $($_.LastWriteTime))"
}
}

Example: Search for Files Modified by Specific User

Scenario: Find files recently modified (requires NTFS and audit logging)

$targetUser = "DOMAIN\jsmith"
$searchPath = "C:\SharedData"
$daysBack = 7

$files = Get-ChildItem -Path $searchPath -Recurse -File | Where-Object {
$_.LastWriteTime -gt (Get-Date).AddDays(-$daysBack)
}

foreach ($file in $files) {
$acl = Get-Acl -Path $file.FullName
# Check owner or audit logs for modification info
if ($acl.Owner -eq $targetUser) {
    [PSCustomObject]@{
        FileName = $file.Name
        Path = $file.DirectoryName
        Modified = $file.LastWriteTime
        Owner = $acl.Owner
    }
}
}

Example: Generate File Inventory Report

Scenario: Create detailed inventory of all files in a directory

$path = "C:\ImportantData"
$reportPath = "C:\Reports\FileInventory_$(Get-Date -Format 'yyyyMMdd').csv"

Get-ChildItem -Path $path -Recurse -File | Select-Object `
Name,
Directory,
Extension,
@{Name="SizeMB";Expression={[math]::Round($_.Length/1MB,2)}},
CreationTime,
LastWriteTime,
LastAccessTime,
IsReadOnly,
@{Name="Age_Days";Expression={(New-TimeSpan -Start $_.CreationTime -End (Get-Date)).Days}} |
Export-Csv -Path $reportPath -NoTypeInformation

Write-Output "Inventory report saved to: $reportPath"

Performance Tips

Use -Filter Instead of -Include

# FASTER - Filter is processed by filesystem
Get-ChildItem -Path C:\Logs -Filter *.log -Recurse

# SLOWER - Include is processed by PowerShell
Get-ChildItem -Path C:\Logs -Include *.log -Recurse

# Filter only supports single pattern
# Use Include when you need multiple patterns: *.log,*.txt

Limit Depth When Possible

# Searches everything (can be slow)
Get-ChildItem -Path C:\ -Recurse

# Limit recursion depth (PowerShell 5.0+)
Get-ChildItem -Path C:\Data -Recurse -Depth 2

# Only current directory
Get-ChildItem -Path C:\Temp

Use -File and -Directory Switches

# Only files (faster than filtering later)
Get-ChildItem -Path C:\Data -Recurse -File

# Only directories
Get-ChildItem -Path C:\Data -Recurse -Directory

# Better than:
Get-ChildItem -Path C:\Data -Recurse | Where-Object {-not $_.PSIsContainer}

Filter Early in Pipeline

# GOOD - Filter as early as possible
Get-ChildItem -Path C:\Logs -Filter *.log -Recurse |
Where-Object {$_.Length -gt 10MB}

# BAD - Gets everything then filters
Get-ChildItem -Path C:\Logs -Recurse |
Where-Object {$_.Extension -eq ".log" -and $_.Length -gt 10MB}

Common Patterns

# Pattern 1: Find files and show size summary
Get-ChildItem -Path C:\Data -Recurse -File |
    Measure-Object -Property Length -Sum |
    Select-Object @{Name="TotalSizeGB";Expression={[math]::Round($_.Sum/1GB,2)}}

# Pattern 2: Group files by extension
Get-ChildItem -Path C:\Temp -Recurse -File |
    Group-Object Extension |
    Select-Object Name, Count |
    Sort-Object Count -Descending

# Pattern 3: Find newest files
Get-ChildItem -Path C:\Downloads -File |
    Sort-Object LastWriteTime -Descending |
    Select-Object -First 10

# Pattern 4: Find files not accessed recently
$unusedDays = 180
Get-ChildItem -Path C:\Archive -Recurse -File | Where-Object {
    $_.LastAccessTime -lt (Get-Date).AddDays(-$unusedDays)
}

# Pattern 5: Search and copy results
Get-ChildItem -Path C:\Source -Filter *.config -Recurse |
    Copy-Item -Destination C:\Backup -Force

# Pattern 6: Find and replace in file contents
Get-ChildItem -Path C:\Scripts -Filter *.ps1 -Recurse | ForEach-Object {
    (Get-Content $_.FullName) -replace 'oldtext','newtext' |
        Set-Content $_.FullName
}

Tips & Tricks

Use Aliases for Speed

# These are all the same
Get-ChildItem -Path C:\Temp
gci C:\Temp
ls C:\Temp
dir C:\Temp

# Aliases are great for interactive use
# Use full cmdlet names in scripts for clarity

Save Search Results for Reuse

# Search once, use many times
$logFiles = Get-ChildItem -Path C:\Logs -Filter *.log -Recurse

# Now you can filter without re-searching
$largeFiles = $logFiles | Where-Object {$_.Length -gt 50MB}
$recentFiles = $logFiles | Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-7)}
$oldFiles = $logFiles | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)}

Combine Select-String with Get-ChildItem

# Find files containing pattern, then get their properties
$matchingFiles = Select-String -Path C:\Logs\*.log -Pattern "error" -List |
Select-Object -ExpandProperty Path

# Now get full file objects
$matchingFiles | ForEach-Object {Get-Item $_} |
Select-Object Name, Length, LastWriteTime

-Recurse on Large Drives

# DON'T do this (will take forever)
Get-ChildItem -Path C:\ -Recurse

# DO this - be specific about where to search
Get-ChildItem -Path C:\Users\$env:USERNAME\Documents -Recurse

# Or limit depth
Get-ChildItem -Path C:\Data -Recurse -Depth 3

Hidden and System Files

# By default, these are not shown
Get-ChildItem -Path C:\Windows

# Use -Force to see hidden/system files
Get-ChildItem -Path C:\Windows -Force

# Or specifically request hidden files
Get-ChildItem -Path C:\Users -Hidden -Recurse

Watch Out for Access Denied Errors

# This will show errors for protected folders
Get-ChildItem -Path C:\ -Recurse

# Suppress errors (be careful!)
Get-ChildItem -Path C:\ -Recurse -ErrorAction SilentlyContinue

# Better: Catch and log errors
Get-ChildItem -Path C:\ -Recurse -ErrorAction SilentlyContinue -ErrorVariable accessErrors
$accessErrors | Out-File C:\Logs\access_errors.log

Common Mistakes

Forgetting Wildcards in -Path

# BAD - This searches for a file literally named "*.txt"
Get-ChildItem -Path "C:\Logs\*.txt"
# Might work, but not if there's no wildcard support in that context

# GOOD - Use -Filter for wildcards (faster)
Get-ChildItem -Path "C:\Logs" -Filter "*.txt"

# OR use -Include with -Recurse
Get-ChildItem -Path "C:\Logs" -Include "*.txt" -Recurse

Using -Include Without -Recurse

# BAD - -Include does nothing without -Recurse!
Get-ChildItem -Path "C:\Logs" -Include "*.log"
# Returns ALL files, ignores -Include

# GOOD - Add -Recurse to make -Include work
Get-ChildItem -Path "C:\Logs" -Include "*.log" -Recurse

# OR use -Filter (works without -Recurse)
Get-ChildItem -Path "C:\Logs" -Filter "*.log"

Not Escaping Special Regex Characters in Select-String

# BAD - Looking for literal "error." but . matches any character
Select-String -Path "C:\Logs\*.log" -Pattern "error."
# Matches "error!", "errorX", etc.

# GOOD - Escape the period
Select-String -Path "C:\Logs\*.log" -Pattern "error\."

# OR use -SimpleMatch for literal strings
Select-String -Path "C:\Logs\*.log" -Pattern "error." -SimpleMatch

Searching Large Directories Without Limiting Scope

# BAD - Searches ENTIRE C: drive (very slow!)
Get-ChildItem -Path C:\ -Recurse -Filter "config.json"

# GOOD - Be specific about where to look
Get-ChildItem -Path "C:\Program Files\MyApp" -Recurse -Filter "config.json"

# BETTER - Use -Depth to limit how deep
Get-ChildItem -Path C:\Data -Recurse -Depth 2 -Filter "config.json"

Additional Resources