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 Exclude This paramater is not mandatory and is in the form of whole or partial name $Exclude = "*proc*" or $Exclude = "/proc" .PARAMETER MaxThreads This parameter is an optional integer $MaxThreads = 100 .PARAMETER Quiet This paramater is an option switch that will turn off visual progress .PARAMETER Force This paramater overrides the prompt about Windows directories in the search path .NOTES Project: https://github.com/tmknight/TMK-CoreModules #> function Find-File { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Please ensure a fully qualified path that must terminate with `"\`".", Position = 0)] [ValidatePattern('\/\w{0,}|([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, ValueFromPipelineByPropertyName = $true, Position = 2)] [string]$Exclude, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 3)] [int]$MaxThreads = 100, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 4)] [Alias('NoProgress')] [switch]$Quiet, [switch]$Force ) Begin { $Path = ($Path).TrimEnd('\*') + '\*' $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 -Exclude "$Exclude" -Directory -Force -ErrorAction SilentlyContinue).FullName if ($dirs -match '\w{1,}') { $level = 'root' if ($dirs -match "$regEx\\Windows\b" -and -not $Force.IsPresent) { $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 -Force -ErrorAction SilentlyContinue).FullName if ($dirsExt0 -match '\w{1,}') { Write-Verbose -Message 'Getting third level directories' $dirsExt1 = ($dirsExt0 | Get-ChildItem -Directory -Force -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" [array]$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 -Exclude "$Exclude" -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 -Exclude "$Exclude" -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`"" } } } |