Private/Get-FileSingleThreaded.ps1
|
#!/usr/bin/env pwsh Set-StrictMode -Version Latest function Get-FileSingleThreaded { <# .SYNOPSIS Returns the files in a directory that match a name pattern, on one thread. .DESCRIPTION The single-threaded recursive search behind Get-FileMultiThreaded. Lists the files in -Directory and, with -Recurse, every directory below it, using .NET enumeration and a stack instead of Get-ChildItem or recursive function calls. Directories whose name is in -Exclude are skipped entirely, so their contents are never read. Directories that cannot be read are skipped instead of stopping the recursion. Symbolic links and junctions are not followed. Hidden and system items are included. Name matching is case insensitive on every platform. This function runs inside Invoke-ParallelJob workers, so it must not call other functions from this module. .PARAMETER Directory The directory to list. Accepts directories from the pipeline. .PARAMETER Include File name patterns to return, such as *.ps1. A file is returned when its name matches any pattern. .PARAMETER Exclude Directory names to skip, such as .git or node_modules. Matched against the directory name only, not its path. .PARAMETER Recurse Also lists every directory below -Directory. Without it, only the files directly in -Directory are returned. .INPUTS System.IO.DirectoryInfo. You can pipe directories to this function. .OUTPUTS System.IO.FileInfo. One object for each matching file. .EXAMPLE Get-FileSingleThreaded -Directory $HOME/Develop -Include '*.ps1' -Exclude '.git' -Recurse Returns every PowerShell script under ~/Develop, skipping .git directories. .EXAMPLE Get-FileSingleThreaded -Directory /etc -Include '*.conf' -Exclude @() Returns the .conf files directly in /etc, without reading subdirectories. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param( [Parameter(Mandatory, ValueFromPipeline, HelpMessage = 'Enter the directory to list. Accepts directories from the pipeline.')] [System.IO.DirectoryInfo]$Directory, [Parameter(Mandatory, HelpMessage = 'Enter the file name patterns to return, such as *.ps1.')] [string[]]$Include, [Parameter(Mandatory, HelpMessage = 'Enter the directory names to skip, such as .git.')] [AllowEmptyCollection()] [string[]]$Exclude, [Parameter(HelpMessage = 'Also list every directory below the directory.')] [switch]$Recurse ) begin { # Setting AttributesToSkip replaces the default of Hidden and System, so only # links are skipped. IgnoreInaccessible skips unreadable directories. $enumerationOptions = [System.IO.EnumerationOptions]@{ IgnoreInaccessible = $true RecurseSubdirectories = $false AttributesToSkip = [System.IO.FileAttributes]::ReparsePoint } $pending = [System.Collections.Generic.Stack[System.IO.DirectoryInfo]]::new() } process { $pending.Push($Directory) while ($pending.Count -gt 0) { $current = $pending.Pop() if ($Recurse) { foreach ($subDirectory in $current.EnumerateDirectories('*', $enumerationOptions)) { if ($subDirectory.Name -notin $Exclude) { $pending.Push($subDirectory) } } } foreach ($file in $current.EnumerateFiles('*', $enumerationOptions)) { foreach ($pattern in $Include) { if ($file.Name -like $pattern) { $file break } } } } } } |