Today I needed to modify a function to remove some bad files that were causing an import to fail across a long list of servers (using Invoke-Command). Here's a sample I've constructed to show and test the removal part.

$tempPath = "C:\Temp"
$filePath = "$tempPath\Test"

# Setup
if (!(Test-Path "$filePath")) {
    New-Item -ItemType Directory "$filePath"
}
Set-Content "$filePath\My_File.txt" "This is some sample content."

# The command in question, we are searching from a parent folder,
# this is important as it triggers the problem.
Get-ChildItem "$tempPath\*My_File*" -Recurse | Remove-Item

# Test the result
if (Test-Path "$localPath\My_File.txt") {
    Write-Host "The file failed to be removed."
} else {
    Write-Host "The file was successfully removed."
}

Straightforward right? And yet every time I ran the procedure (which takes a long time) and checked those files, they were still hanging around and the import was crashing.

I mapped a server drive from my workstation and executed the command myself (without the Remove-Item) and it worked. I re-executed the command on remote servers, it did nothing. I added debugging statements, and after a long wait it turns out the files were not being found at all.

How is that possible? Looking at Get-Help for Get-ChildItem there are multiple parameter sets, so the first step was to find which parameter is in used by default. This is how you inspect parameter or argument binding when calling a PowerShell function:

Trace-Command -Name ParameterBinding -PsHost -Expression { 
	Get-ChildItem "$localPath\*FileName*" -Recurse 
}
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-ChildItem]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Get-ChildItem]
DEBUG: ParameterBinding Information: 0 :     BIND arg ["C:\Temp\*FileName*"] to parameter [Path]
DEBUG: ParameterBinding Information: 0 :         Binding collection parameter Path: argument type [String], parameter
type [System.String[]], collection type Array, element type [System.String], no coerceElementType
DEBUG: ParameterBinding Information: 0 :         Creating array with element type [System.String] and 1 elements
DEBUG: ParameterBinding Information: 0 :         Argument type String is not IList, treating this as scalar
DEBUG: ParameterBinding Information: 0 :         Adding scalar element of type String to array position 0
DEBUG: ParameterBinding Information: 0 :         BIND arg [System.String[]] to param [Path] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : BIND cmd line args to DYNAMIC parameters.
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-ChildItem]
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing

I do know that my workstation is using v3 and my servers are using v2, so I inspected -Path on both with Get-Help -Full …

PowerShell v2

-Path 
        Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory (.).

        Required?                    false
        Position?                    1
        Default value
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  false

PowerShell v3

-Path 
        Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory (.).

        Required?                    false
        Position?                    1
        Default value                Current directory
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  true


Did you spot it? v2 says that it accepts wildcards in the description but not in the parameter value. As it turns out, it does not fully support wildcards in the same way that v3 does. Specifically, this line above fails to work as intended:

Get-ChildItem "$tempPath\*My_File*" -Recurse | Remove-Item

And this slightly altered version works fine, and so should be used more often as it's cross-version compatible:

Get-ChildItem "$tempPath" "*My_File*" -Recurse | Remove-Item