lit
lit

Reputation: 16236

PowerShell and cmd returning different results

Why are these returning different results? What am I missing about the cmd command?

PS C:\src\t\cad> Get-ChildItem -Directory -Recurse -Path $Env:ProgramFiles -ErrorAction SilentlyContinue | Measure-Object

Count    : 17381

And from cmd.exe:

10:28:18.87  C:\src\t\cad
C:>powershell -NoProfile -Command "Get-ChildItem -Directory -Recurse -Path '"%ProgramFiles%"' -ErrorAction SilentlyContinue ^| Measure-Object"

Count    : 0

Upvotes: 2

Views: 348

Answers (3)

committhisgit
committhisgit

Reputation: 110

I think the -ErrorAction SilentlyContinue is suppressing an error in the first command so then nothing is being passed down the pipeline to Measure-Object.

Testing this on PSCore I get:

pwsh -command "get-process ^| measure-object"
get-process : Cannot find a process with the name "^".
Count    : 0

But removing the '^' character:

bash-3.2$ pwsh -command "get-process | measure-object"
Count    : 318

So I assume that's the same for Windows PowerShell. Try without the ^

powershell -NoProfile -Command "Get-ChildItem -Directory -Recurse -Path '"%ProgramFiles%"' -ErrorAction SilentlyContinue | Measure-Object"

Upvotes: 1

briantist
briantist

Reputation: 47792

This is happening because of the caret ^. You are trying to escape the pipe | for the command interpreter, but you don't need to because it's already in a quoted string, so it doesn't get interpreted and gets sent directly to PowerShell.

As a result, PowerShell sees this:

Get-ChildItem -Directory -Recurse -Path 'C:\Program Files' -ErrorAction SilentlyContinue ^| Measure-Object

So why doesn't that work?

Parameters to PowerShell commands are separated by spaces, and when a parameter is not named specifically it's treated positionally, if possible.

Let's look at a simpler example to demonstrate:

Get-ChildItem -Path . ^

Similarly you'll see no output, but with a trace, we can see why:

Trace-Command -Name ParameterBinding -Expression { Get-ChildItem -Path . ^ } -PSHost
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-ChildItem]
DEBUG: ParameterBinding Information: 0 :     BIND arg [.] to parameter [Path]
DEBUG: ParameterBinding Information: 0 :         COERCE arg to [System.String[]]
DEBUG: ParameterBinding Information: 0 :             Trying to convert argument value from System.String to
System.String[]
DEBUG: ParameterBinding Information: 0 :             ENCODING arg into collection
DEBUG: ParameterBinding Information: 0 :             Binding collection parameter Path: argument type [String],
parameter type [System.String[]], collection type Array, element type [System.String], 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 :             COERCE arg to [System.String]
DEBUG: ParameterBinding Information: 0 :                 Parameter and arg types the same, no coercion is needed.
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 POSITIONAL cmd line args [Get-ChildItem]
DEBUG: ParameterBinding Information: 0 :     BIND arg [^] to parameter [Filter]
DEBUG: ParameterBinding Information: 0 :         BIND arg [^] to param [Filter] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : BIND cmd line args to DYNAMIC parameters.
DEBUG: ParameterBinding Information: 0 :     DYNAMIC parameter object:
[Microsoft.PowerShell.Commands.GetChildDynamicParameters]
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-ChildItem]
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing

That caret was bound positionally to the -Filter parameter. It seems the filtering is rather lenient in that it doesn't care if the value is nonsense; I guess it just treats it as non-matching and doesn't throw an error.

Per mklement0's comment:

^ is technically a valid filename, and -Filter accepts filename (patterns); thus, if you had a file literally named ^, it would be matched. Using illegal-in-a-filename characters (which vary by platform) does break, though the helpfulness of the error messages varies.

So what you have with -Filter ^ is a filter that never matches, hence 0 results.

Upvotes: 3

mklement0
mklement0

Reputation: 437363

To complement briantist's helpful answer with the cmd.exe perspective:

  • In cmd.exe, ^ only serves as the escape character in unquoted strings.

  • Inside double-quoted strings ("..."):

    • the only character that needs escaping is " itself.
    • ^ has no special meaning and is retained as-is.

Given that the | character you intend to pass through to PowerShell is inside "...", you therefore need not - and must not - use ^ to escape it.

Upvotes: 1

Related Questions