Public/Find-Library.ps1

#Requires -Version 7.0

$CSharpCode = @'
using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
 
public class FastFileSearch
{
    public static List<string> FindFile(string searchPath, string fileName, bool caseSensitive = false)
    {
        var results = new List<string>();
        var comparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
        var enumerationOptions = new EnumerationOptions
        {
            IgnoreInaccessible = true,
            RecurseSubdirectories = true,
            ReturnSpecialDirectories = false
        };
 
        try
        {
            var files = Directory.EnumerateFiles(searchPath, fileName, enumerationOptions);
            results.AddRange(files);
        }
        catch (Exception)
        {
            // Skip directories we can't access
        }
 
        return results;
    }
 
    public static List<string> FindFiles(string searchPath, string[] filePatterns, bool caseSensitive = false)
    {
        var results = new List<string>();
 
        foreach (var pattern in filePatterns)
        {
            var enumerationOptions = new EnumerationOptions
            {
                IgnoreInaccessible = true,
                RecurseSubdirectories = true,
                ReturnSpecialDirectories = false
            };
 
            try
            {
                var files = Directory.EnumerateFiles(searchPath, pattern, enumerationOptions);
                results.AddRange(files);
            }
            catch (Exception)
            {
                // Skip directories we can't access
            }
        }
 
        return results.Distinct().ToList();
    }
 
    public static List<string> FindFileParallel(string searchPath, string fileName, int? maxDegreeOfParallelism = null)
    {
        var results = new ConcurrentBag<string>();
        var enumerationOptions = new EnumerationOptions
        {
            IgnoreInaccessible = true,
            RecurseSubdirectories = false,
            ReturnSpecialDirectories = false
        };
 
        if (!maxDegreeOfParallelism.HasValue)
        {
            maxDegreeOfParallelism = Environment.ProcessorCount;
        }
 
        // Search root directory first
        try
        {
            var rootFiles = Directory.EnumerateFiles(searchPath, fileName, new EnumerationOptions
            {
                IgnoreInaccessible = true,
                RecurseSubdirectories = false
            });
 
            foreach (var file in rootFiles)
            {
                results.Add(file);
            }
        }
        catch (Exception)
        {
            // Skip if we can't access root
        }
 
        // Get all subdirectories to process in parallel
        var directories = new ConcurrentQueue<string>();
        try
        {
            var rootDirs = Directory.EnumerateDirectories(searchPath, "*", enumerationOptions);
            foreach (var dir in rootDirs)
            {
                directories.Enqueue(dir);
            }
        }
        catch (Exception)
        {
            return results.ToList();
        }
 
        // Process directories in parallel
        var parallelOptions = new ParallelOptions
        {
            MaxDegreeOfParallelism = maxDegreeOfParallelism.Value
        };
 
        Parallel.ForEach(directories, parallelOptions, directory =>
        {
            try
            {
                var filesInDir = Directory.EnumerateFiles(directory, fileName, new EnumerationOptions
                {
                    IgnoreInaccessible = true,
                    RecurseSubdirectories = true
                });
 
                foreach (var file in filesInDir)
                {
                    results.Add(file);
                }
            }
            catch (Exception)
            {
                // Silently skip directories we can't access
            }
        });
 
        return results.ToList();
    }
 
    public static List<string> FindFilesParallel(string searchPath, string[] filePatterns, int? maxDegreeOfParallelism = null)
    {
        var results = new ConcurrentBag<string>();
        var enumerationOptions = new EnumerationOptions
        {
            IgnoreInaccessible = true,
            RecurseSubdirectories = false,
            ReturnSpecialDirectories = false
        };
 
        if (!maxDegreeOfParallelism.HasValue)
        {
            maxDegreeOfParallelism = Environment.ProcessorCount;
        }
 
        // Search root directory first for all patterns
        foreach (var pattern in filePatterns)
        {
            try
            {
                var rootFiles = Directory.EnumerateFiles(searchPath, pattern, new EnumerationOptions
                {
                    IgnoreInaccessible = true,
                    RecurseSubdirectories = false
                });
 
                foreach (var file in rootFiles)
                {
                    results.Add(file);
                }
            }
            catch (Exception)
            {
                // Skip if we can't access root
            }
        }
 
        // Get all subdirectories to process in parallel
        var directories = new ConcurrentQueue<string>();
        try
        {
            var rootDirs = Directory.EnumerateDirectories(searchPath, "*", enumerationOptions);
            foreach (var dir in rootDirs)
            {
                directories.Enqueue(dir);
            }
        }
        catch (Exception)
        {
            return results.Distinct().ToList();
        }
 
        // Process directories in parallel
        var parallelOptions = new ParallelOptions
        {
            MaxDegreeOfParallelism = maxDegreeOfParallelism.Value
        };
 
        Parallel.ForEach(directories, parallelOptions, directory =>
        {
            foreach (var pattern in filePatterns)
            {
                try
                {
                    var filesInDir = Directory.EnumerateFiles(directory, pattern, new EnumerationOptions
                    {
                        IgnoreInaccessible = true,
                        RecurseSubdirectories = true
                    });
 
                    foreach (var file in filesInDir)
                    {
                        results.Add(file);
                    }
                }
                catch (Exception)
                {
                    // Silently skip directories we can't access
                }
            }
        });
 
        return results.Distinct().ToList();
    }
}
'@


Add-Type -TypeDefinition $CSharpCode

function Find-FileRecursive {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName', 'PSPath')]
        [string[]]$Path,

        [Parameter(Mandatory = $true)]
        [string[]]$FilePattern,

        [Parameter(Mandatory = $false)]
        [switch]$Parallel,

        [Parameter(Mandatory = $false)]
        [int]$MaxThreads = [Environment]::ProcessorCount
    )

    begin {
        $AllResults = [System.Collections.Generic.List[string]]::new()
    }

    process {
        foreach ($SearchPath in $Path) {
            try {
                $ResolvedPaths = Resolve-Path -Path $SearchPath -ErrorAction Stop
            } catch {
                Write-Warning "Path not found or inaccessible: $SearchPath"
                continue
            }

            foreach ($ResolvedPath in $ResolvedPaths) {
                $ActualPath = $ResolvedPath.Path

                if (-not (Test-Path -Path $ActualPath -PathType Container)) {
                    Write-Warning "Path is not a directory: $ActualPath"
                    continue
                }

                Write-Verbose "Searching in: $ActualPath"

                $Results = if ($FilePattern.Count -eq 1) {
                    if ($Parallel) {
                        [FastFileSearch]::FindFileParallel($ActualPath, $FilePattern[0], $MaxThreads)
                    } else {
                        [FastFileSearch]::FindFile($ActualPath, $FilePattern[0], $false)
                    }
                } else {
                    if ($Parallel) {
                        [FastFileSearch]::FindFilesParallel($ActualPath, $FilePattern, $MaxThreads)
                    } else {
                        [FastFileSearch]::FindFiles($ActualPath, $FilePattern, $false)
                    }
                }

                if ($null -ne $Results -and $Results.Count -gt 0) {
                    foreach ($Result in $Results) {
                        $AllResults.Add($Result)
                    }
                }
            }
        }
    }

    end {
        # Return unique results as FileInfo objects
        $UniqueResults = $AllResults | Select-Object -Unique | ForEach-Object { Get-Item -LiteralPath $_ }
        $UniqueResults
    }
}

<# Example Usage:
    Write-Verbose "System has $([Environment]::ProcessorCount) processor cores."
 
    $Patterns = @(
        "Microsoft.Identity.Client.dll",
        "Microsoft.IdentityModel.*.dll",
        "Microsoft.Identity.Client.*.dll"
    )
 
    Write-Host "Searching for patterns:" -ForegroundColor Cyan
    $Patterns | ForEach-Object { Write-Host " - $_" -ForegroundColor Gray }
    Write-Host ""
 
    $Timer1 = [System.Diagnostics.Stopwatch]::StartNew()
    $Results1 = Find-FileRecursive -Path $SearchPath -FilePattern $Patterns -Parallel -Verbose
    $Timer1.Stop()
    Write-Host "`$Results1: Found $($Results1.Count) file(s) in $($Timer1.Elapsed.TotalSeconds.ToString('F2')) seconds" -ForegroundColor Green
    Write-Host ""
 
    Write-Host "Example 2: Multiple paths as array" -ForegroundColor Yellow
    $MultiplePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator | Where-Object { Test-Path $_ -PathType Container }
    $Timer2 = [System.Diagnostics.Stopwatch]::StartNew()
    $Results2 = Find-FileRecursive -Path $MultiplePaths -FilePattern $Patterns -Parallel -Verbose
    $Timer2.Stop()
    Write-Host "`$Results2: Found $($Results2.Count) file(s) in $($Timer2.Elapsed.TotalSeconds.ToString('F2')) seconds" -ForegroundColor Green
    Write-Host ""
 
    Write-Host "Example 3: Pipeline input from Get-ChildItem" -ForegroundColor Yellow
    $RootPath = if ($IsWindows -or $PSVersionTable.PSVersion.Major -lt 6)
    {
        "C:\Program Files"
    }
    elseif ($IsMacOS)
    {
        "/Applications"
    }
    else
    {
        "/usr"
    }
    $Timer3 = [System.Diagnostics.Stopwatch]::StartNew()
    $Results3 = Get-ChildItem -Path $RootPath -Directory -ErrorAction SilentlyContinue |
        Select-Object -First 5 |
        Find-FileRecursive -FilePattern $Patterns -Parallel -Verbose
    $Timer3.Stop()
    Write-Host "`$Results3: Found $($Results3.Count) file(s) in $($Timer3.Elapsed.TotalSeconds.ToString('F2')) seconds" -ForegroundColor Green
    Write-Host ""
 
    Write-Host "Example 4: Pipeline input from string array" -ForegroundColor Yellow
    $Timer4 = [System.Diagnostics.Stopwatch]::StartNew()
    $Results4 = $MultiplePaths | Find-FileRecursive -FilePattern $Patterns -Parallel -Verbose
    $Timer4.Stop()
    Write-Host "`$Results4: Found $($Results4.Count) file(s) in $($Timer4.Elapsed.TotalSeconds.ToString('F2')) seconds" -ForegroundColor Green
    Write-Host ""
#>