When working with PowerShell's Get-ChildItem (gci)
or dir
, you're limited to searching within a single drive at a time. This becomes problematic when:
- You need comprehensive system-wide searches
- Target files may exist on any mounted storage
- Manual drive enumeration would be inefficient
First, let's retrieve all logical drives without hardcoding them:
$allDrives = [System.IO.DriveInfo]::GetDrives() |
Where-Object { $_.DriveType -eq 'Fixed' -or $_.DriveType -eq 'Removable' } |
Select-Object -ExpandProperty Name
Here's a robust function that searches across all drives:
function Search-AllDrives {
param(
[string]$fileName,
[string]$fileExtension = '*',
[switch]$useIndex
)
$drives = [System.IO.DriveInfo]::GetDrives() |
Where-Object { $_.IsReady -and ($_.DriveType -eq 'Fixed' -or $_.DriveType -eq 'Removable') }
if ($useIndex) {
foreach ($drive in $drives) {
$query = "System.FileName:~"$fileName" AND System.FileExtension:"$fileExtension""
$searcher = New-Object -ComObject Microsoft.Search.Interop.CSearchManager
$catalog = $searcher.GetCatalog('SystemIndex')
$queryHelper = $catalog.GetQueryHelper()
$queryHelper.QueryWhereRestrictions = $query
$queryHelper.QueryContentLocale = [System.Globalization.CultureInfo]::CurrentUICulture.LCID
$results = $queryHelper.QueryExec()
while (!$results.EOS) {
$results.GetDisplayUrl()
$results.MoveNext()
}
}
}
else {
$drives | ForEach-Object {
Get-ChildItem -Path "$($_.Name)\" -Filter "*$fileName*.$fileExtension" -Recurse -ErrorAction SilentlyContinue
}
}
}
For significantly faster results, leverage the Windows Search index:
# Requires Windows Search service running
Search-AllDrives -fileName "report" -fileExtension "xlsx" -useIndex
- Indexed searches are 10-100x faster than filesystem scans
- Add
-ErrorAction SilentlyContinue
to skip permission errors - For network drives, ensure they're indexed in Windows Search settings
# Find all PDFs modified in the last 30 days
Search-AllDrives -fileName "*" -fileExtension "pdf" |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-30) }
# Case-insensitive search with size filtering
$results = Search-AllDrives -fileName "CONFIG" -fileExtension "json"
$results | Where-Object { $_.Length -gt 1KB -and $_.Length -lt 1MB }
When working with PowerShell, you might need to search for files across all available drives on a system. While Get-ChildItem
(aliased as gci
or dir
) is useful, it has limitations when searching across multiple drives. This article explores efficient methods for system-wide file searches, including leveraging Windows Search Index for better performance.
The standard Get-ChildItem
approach requires manually enumerating and looping through drives:
# Basic multi-drive search
Get-PSDrive -PSProvider FileSystem | ForEach-Object {
Get-ChildItem -Path $_.Root -Recurse -Filter "*.txt" -ErrorAction SilentlyContinue
}
This method is slow for large drives and doesn't utilize Windows' built-in search capabilities.
For significantly faster results, we can query the Windows Search Index through COM objects:
# Create search COM object
$search = New-Object -ComObject "Microsoft.Search.Interop.CSearchManager"
$searchQuery = $search.GetCatalog("SystemIndex").GetQueryHelper()
# Set query parameters (search all drives)
$searchQuery.QueryMaxResults = 1000
$searchQuery.QuerySelectColumns = "System.ItemPathDisplay"
$searchQuery.QueryWhereRestrictions = "System.FileName LIKE '%.txt'"
# Execute the query
$results = $searchQuery.QueryParsed("System.Kind: = 'document'")
$results | ForEach-Object { $_.GetValue("System.ItemPathDisplay") }
For more complex scenarios, consider these approaches:
# Using WMI to get all logical disks first
$drives = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 }
# Parallel processing with Runspaces
$runspacePool = [RunspaceFactory]::CreateRunspacePool(1, 10)
$runspacePool.Open()
$jobs = @()
foreach ($drive in $drives) {
$scriptBlock = {
param($driveLetter)
Get-ChildItem -Path "${driveLetter}:\" -Recurse -Filter $args[0] -ErrorAction SilentlyContinue
}
$ps = [PowerShell]::Create().AddScript($scriptBlock).AddArgument("*.ps1").AddArgument($drive.DeviceID)
$ps.RunspacePool = $runspacePool
$jobs += [PSCustomObject]@{
Pipe = $ps
Async = $ps.BeginInvoke()
}
}
# Wait for completion and collect results
$results = $jobs | ForEach-Object { $_.Pipe.EndInvoke($_.Async) }
$runspacePool.Close()
When dealing with large file systems:
- The Windows Search Index method is fastest but may miss recently added files
- Parallel processing with Runspaces improves performance for traditional searches
- Consider filtering at the filesystem level (extension, date, size) before processing
Always include proper error handling for inaccessible paths:
try {
Get-ChildItem -Path "C:\" -Recurse -Filter "*.log" -ErrorAction Stop
}
catch [System.UnauthorizedAccessException] {
Write-Warning "Access denied to some paths"
}
catch {
Write-Warning "Other error occurred: $_"
}