How comes `if (Test-Path...)` actually works?

In PowerShell, syntax for if is as so:

if (<test1>)
    {<statement list 1>}
[elseif (<test2>)
    {<statement list 2>}]
    {<statement list 3>}]

Another syntax rule is that for subexpressions, you need to use parentheses like this:

 write-output (get-date)

So with these two rules combined, I would expect that the test for some path needs to be written with two sets of parentheses like this:

if ((Test-Path ...)) {
    # do something

However, this also works:

if (Test-Path ...) {
    # do something

and just for the sake of completeness, this doesn't work:

if (!Test-Path ...) {
    # do something

(here, you would need to wrap the subexpression in parenthesis as usual).

Can anyone explain the syntax rules that apply here and how comes that I can use the IF test with one parenthesis only? Is it some PowerShell magic or am I misunderstanding the basic syntax rules?


Referring to C.2.2 from Appendix C: The PowerShell grammar in Bruce Payette's Windows PowerShell in Action, we have:

<ifStatementRule> =
  'if' '(' <pipelineRule> ')' <statementBlockRule>
  [ 'elseif' '(' <pipelineRule> ')' <statementBlockRule> ]*
  [ 'else' <statementBlockRule> ]{0|1}

This indicates the ( and ) tokens as part of the literal syntax for recognizing an if statement, and that the <test> from the about_If documentation refers to a pipeline that will be resolved to a Boolean.

Following the pipeline rules, we find:

  • Test-Path ... parses to a <cmdletCall> of <name> <parameterArgumentToken>,
  • !Test-Path ... results in an <expressionRule> of <UnaryOperatorToken> <propertyOrArrayReferenceRule>, which fails when the cmdlet call cannot match the simple property or array rule, whereas
  • !(Test-Path ...) is able to match the parenthesized cmdlet call as a sub-expression.

Edit: See also PowerShell 2.0 Language Specification (thanks to Roman's answer to another question).

The parentheses after the if define a subexpression (if parentheses were required around Test-Path, then we would need parens around $num -eq 5 and every other expression).. The additional parentheses after the not operator is required because Test-Path needs to be evaluated before it can be negated. You can try this without an if statement.

This does not work:

PS> !Test-Path NonExistent.file

This does work:

PS> !(Test-Path NonExistent.file)

Need Your Help

DBO rights risk

security dbo

I'm advising a friend who manages a SQL 2k5 box that has several users who have dbo access to multiple databases. The problem is:

Python User-Defined Global Variables Across Modules: Issues calling them when other modules are run

python variables module user-defined

I am fairly new to Python and currently learning to work with functions and multiple modules within a python program.