Glob.psm1
$functionDir = Join-Path -Path $PSScriptRoot -ChildPath 'Functions' if( (Test-Path -Path $functionDir) ) { foreach( $item in (Get-ChildItem -Path $functionDir -Filter '*.ps1') ) { . $item.FullName } } function Find-GlobFile { <# .SYNOPSIS Searches for files using advanced wildcard/glob syntax. .DESCRIPTION The `Find-GlobFile` function searches directories for files using `*` and `**` wildcard/glob patterns. Pass the top-level directories to search to the `Path` parameter. Only files under these directories will be returned. By default, all files are returned (i.e. the function uses `**/*` as the pattern). Pass glob/wildcard patterns to the `Include` pattern. Only files that match that pattern will be included. To exclude files, pass glob/wildcard patterns to the `Exclude` parameter. Supported patterns are: * `*`: matches zero or more characters in a directory or file name *except* the directory separator character * `**`: matches zero or more characters in a directory or file name's path, i.e. it matches the directory separator character * `?`: match exactly one character * `[abc]`: match one of the characters inside the brackets * `[a-z]`: matches one character from the range inside the brackets * `[!abc]`: matches any one character *not* inside the brackets * `[!a-z]`: matches one character that is *not* in the range defined in the brackets By default, the search is case-insensitive. To peform a case-sensitive search, use the `CaseSensitive` switch. By default, hidden files and directories are not searched. To include hidden files, use the `Force` switch. To troubleshoot which files `Find-GlobFile` is including/excluding/finding, set your `$DebugPreference` to `Continue`. You'll see three columns of output: 1. Either empty to indicate a pattern included a file, or contains an exclamation mark, "!", to indicate a file was excluded by a pattern. 2. The pattern. 3. The file that matched the pattern. Here's an example: DEBUG: **/* file.txt DEBUG: ! **/*.orig file.txt.orig The `Find-GlobFile` function uses the [DotNet.Glob library](https://www.nuget.org/packages/DotNet.Glob). .EXAMPLE Find-GlobFile -Path 'dir1','dir2' Returns all files under `dir`` and `dir2` in the current directory. .EXAMPLE Find-GlobFile -Path 'dir1' -Include '*.ps1' Returns all `*.ps1` files in the `dir1` directory. .EXAMPLE Find-GlobFile -Path '.' -Include '**/*.ps1' Returns all `*.ps1` files under the current directory and all its sub-directories. .EXAMPLE Find-GlobFile -Path '.' -Include '**/*.ps1' -Exclude '**/*.Tests.ps1' Returns all `*.ps1` files except files that match `*.Tests.ps1` under the current directory and all its sub-directories. .EXAMPLE Find-GlobFile -Path '.' -Include 'Find-GlobFile.ps1' -CaseSensitive Demonstrates how to do a case-sensitive search. .EXAMPLE Find-GlobFile -Path '.' -Include '**/*.txt' -Force Demonstrates how to search hidden files and directories by using the `Force`. #> [CmdletBinding()] [OutputType([IO.FileInfo])] param( [Parameter(Mandatory)] # The directories to search. Relative paths are evaluated from the current directory. [String[]]$Path, # The files to include. By default all files in and under each directory in `Path` are returned. [String[]]$Include = ('**{0}*' -f [IO.Path]::DirectorySeparatorChar), # Any files to exclude. By default, no files are excluded. Any file that gets included that matches an exclude pattern is not returned. [String[]]$Exclude, # By default, the search is case-insensitive. To perform a case-sensitive search, use this switch. [switch]$CaseSensitive, # Include hidden files, too. [switch]$Force ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $errorActionParam = @{ } if( $ErrorActionPreference -eq [Management.Automation.ActionPreference]::Ignore ) { $errorActionParam['ErrorAction'] = [Management.Automation.ActionPreference]::Ignore } function Test-GlobMatch { param( [object]$InputObject, [switch]$IsDirectory ) $relativePath = Resolve-Path -LiteralPath $InputObject.FullName -Relative @errorActionParam if( -not $relativePath ) { return $false } if( $relativePath.Length -ge 2 -and ` $relativePath[0] -eq '.' -and ` ($relativePath[1] -eq [IO.Path]::DirectorySeparatorChar -or ` $relativePath[1] -eq [IO.Path]::AltDirectorySeparatorChar)) { # Remove the .\ or ./ at the beginning of the path, as the glob library doesn't like it. $relativePath = $relativePath.Substring(2) } $result = ' ' $whatMatched = '' $showMessage = -not $IsDirectory try { if( -not $IsDirectory ) { $isIncluded = $false foreach( $includeGlob in $includeGlobs ) { if( $includeGlob.IsMatch($relativePath) ) { $whatMatched = $includeGlob $isIncluded = $true break } } if( -not $isIncluded ) { return $false } } $isIncluded = $true foreach( $excludeGlob in $excludeGlobs ) { if( $excludeGlob.IsMatch($relativePath) ) { $showMessage = $true $result = '!' $whatMatched = $excludeGlob $isIncluded = $false break } } return $isIncluded } finally { if( $showMessage ) { Write-Debug ($outputFormat -f $result, $whatMatched, $relativePath) } } } function Find-GlobFileMatch { param( [object[]]$Item ) foreach( $info in (Get-ChildItem -LiteralPath $Item.FullName -Force:$Force) ) { # Recurse into directories that aren't exluded. if( $info.PSIsContainer ) { if( -not (Test-GlobMatch $info -IsDirectory) ) { continue } Find-GlobFileMatch $info continue } if( (Test-GlobMatch $info) ) { Write-Output $info } } } $stats = & { $Include $Exclude } | Where-Object { $_ } | Measure-Object -Property 'Length' -Maximum $outputFormat = '{{0}} {{1,-{0}}} {{2}}' -f $stats.Maximum foreach( $rootPath in $Path ) { $rootPath = Resolve-Path -Path $rootPath | Select-Object -ExpandProperty 'ProviderPath' if( -not $rootPath ) { continue } $options = New-Object 'DotNet.Globbing.GlobOptions' $options.Evaluation.CaseInsensitive = -not $CaseSensitive function ConvertTo-Glob { param( [Parameter(Mandatory,ValueFromPipeline)] [String]$InputObject ) process { return [DotNet.Globbing.Glob]::Parse($InputObject,$options) } } $includeGlobs = $Include | Where-Object { $_ } | ConvertTo-Glob $excludeGlobs = $Exclude | Where-Object { $_ } | ConvertTo-Glob Push-Location -Path $rootPath try { Find-GlobFileMatch -Item (Get-Item -Path $rootPath) } finally { Pop-Location } } } # Copyright 2012 Aaron Jensen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every function/cmdlet call in your function. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` attribute. $Cmdlet, [Parameter(Mandatory)] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. [Management.Automation.SessionState]$SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } |