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`"" } } } |