PowerShell: Search Files Across All Drives with System Index Integration


6 views

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: $_"
}