public/Find-File.ps1

<#
.SYNOPSIS
    Module for finding files using multithreading
.DESCRIPTION
    Use this module to find files faster than can be accomplished with Get-ChildItem alone
.EXAMPLE
    Find-File -Path $pth -File "nmap-update.exe"

    C:\Program Files (x86)\Nmap\nmap-update.exe
.PARAMETER Path
    This parameter is manadatory and is in the form of an absolute path.
    The path must be terminiated with "\"

    $pth = "c:\"

    or

    $pth = "\\server\c$\"
.PARAMETER File
    This paramater is mandatory and is in the form of whole or partial name

    $file = "nmap-update.exe"

    or

    $file = "nmap-update"
.PARAMETER MaxThreads
    This parameter is an optional integer

    $MaxThreads = 100
.PARAMETER Quiet
    This paramater is an option switch that will turn off visual progress
.NOTES
    Author: Travis M Knight; tmknight
    Date: 2019-04-16: tmknight: Inception
    Date: 2022-07-20: tmknight: New logic to allow for UNC paths and avoid double-seraching the source path
    Date: 2022-08-18: tmknight: New logic to get search path into more threads to speed up search
#>


function Find-File {
    param(
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Please ensure a fully qualified path that must terminate with `"\`".",
            Position = 0)]
        [ValidatePattern("([a-zA-Z]\:|\\\\\w{1,}(\.{1}\w{1,}){0,}\\[a-zA-Z]{1,}\$)\\\w*")]
        [string]$Path,
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 1)]
        [string]$File,
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            Position = 2)]
        [int]$MaxThreads = 100,
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            Position = 3)]
        [Alias("NoProgress")]
        [switch]$Quiet
    )

    Begin {
        $regExPath = ($Path -replace "\\", "\\" -replace "\$", "\$").TrimEnd('\')
        $regEx = "([a-zA-Z]\:|\\\\\w{1,}(\.{1}\w{1,}){0,}\\[a-zA-Z]{1,}\$)"
        try {
            Write-Verbose -Message "Getting root path directories"
            $dirs = (Get-ChildItem -Path $Path -Directory -ErrorAction SilentlyContinue).FullName

            if ($dirs -match "\w{1,}") {
                $level = "root"
                if ($dirs -match "$regEx\\Windows\b") {
                    $title = 'A "Windows" directory is in your search; this may take a very long time to complete.'
                    $prompt = ''
                    $abort = New-Object System.Management.Automation.Host.ChoiceDescription '&Abort', 'Aborts the operation'
                    $continue = New-Object System.Management.Automation.Host.ChoiceDescription '&Continue', 'Continues the operation'
                    $options = [System.Management.Automation.Host.ChoiceDescription[]] ($abort, $continue)
                    $choice = $host.ui.PromptForChoice($title, $prompt, $options, 0)
                    if ($choice -eq 0) {
                        break
                    }
                }

                if ($dirs.Count -le 50) {
                    Write-Verbose -Message "Getting second level directories"
                    $dirsExt0 = ($dirs | Get-ChildItem -Directory -ErrorAction SilentlyContinue).FullName
                    if ($dirsExt0 -match "\w{1,}") {
                        Write-Verbose -Message "Getting third level directories"
                        $dirsExt1 = ($dirsExt0 | Get-ChildItem -Directory -ErrorAction SilentlyContinue).FullName
                    }

                    Write-Verbose -Message "Determining valid search paths"
                    $dirs = $dirs -replace "$regExPath$" | Where-Object { $_.trim() -ne "" }
                    if ($dirsExt0 -match "\w{1,}") {
                        $level = "second"
                        $dirsExt0 = $dirsExt0 -replace "$regExPath$" | Where-Object { $_.trim() -ne "" }
                        if ($dirsExt1 -notmatch "\w{1,}" -or $dirsExt1 -gt 1000) {
                            $dirsExt1 = $dirsExt0
                            $dirsExt0 = $null
                        }
                        else {
                            $dirsExt1 = $dirsExt1 -replace "$regExPath$" | Where-Object { $_.trim() -ne "" }
                            $level = "third"
                        }
                    }
                    else {
                        $dirsExt1 = $dirs
                        $dirs = $null
                    }
                }
                else {
                    $dirsExt1 = $dirs
                    $dirs = $null
                }
            }
            else {
                Write-Error -Message "The path indicated does not appear to be valid. Please ensure a fully qualified path and that it is accessible: $Path"
            }
        }
        catch {
            $_
            break
        }

        $block = {
            param($dir, $File)
            $result = @()
            try {
                $i = Get-ChildItem -Path "$dir\*" -Filter "*$File*" -File -Recurse -Force -ErrorAction SilentlyContinue
                if ($i) {
                    foreach ($o in $i | Where-Object -Property FullName -Value $File -Match) {
                        $result += [PSCustomObject]@{
                            File = $o.FullName
                        }
                    }
                    return $result
                }
            }
            catch {
                $_
                break
            }
        }
    }
    Process {
        Write-Verbose -Message "Searching $level level directories"
        $out = Start-Multithreading -InputObject $dirsExt1 -ScriptBlock $block -ArgumentList (, $File) -MaxThreads $MaxThreads -Quiet:$Quiet | Sort-Object -Property File -Unique

        ## Search root of $Path and extended root directories
        Write-Verbose -Message "Searching root path"
        $in = (Get-ChildItem -Path $Path -Filter "*$File*" -File -Force -ErrorAction SilentlyContinue).FullName | Where-Object { $null -ne $_ }
        if ($null -eq $out -and $null -ne $in) {
            $in | ForEach-Object {
                $out += [PSCustomObject]@{
                    File = $_
                }
            }
        }
        else {
            $out += $in
        }

        if ($dirsExt0 -match "\w{1,}") {
            Write-Verbose -Message "Searching second level directories"
            $in0 = (Get-ChildItem -Path $dirsExt0 -Filter "*$File*" -File -Force -ErrorAction SilentlyContinue).FullName | Where-Object { $null -ne $_ }
            if ($null -eq $out -and $null -ne $in0) {
                $in0 | ForEach-Object {
                    $out += [PSCustomObject]@{
                        File = $_
                    }
                }
            }
            else {
                $out += $in0
            }
        }

        if ($dirs -match "\w{1,}") {
            Write-Verbose -Message "Searching root path directories"
            $in1 = (Get-ChildItem -Path $dirs -Filter "*$File*" -File -Force -ErrorAction SilentlyContinue).FullName | Where-Object { $null -ne $_ }
            if ($null -eq $out -and $null -ne $in1) {
                $in1 | ForEach-Object {
                    $out += [PSCustomObject]@{
                        File = $_
                    }
                }
            }
            else {
                $out += $in1
            }
        }
    }
    End {
        if ($out -match "\w{1,}") {
            return $out
        }
        else {
            Write-Warning -Message "No files were found matching `"$File`" under the root of `"$Path`""
        }
    }
}