In the code below, why does the [int] type assignment for name1 get ignored and is actually treated as part of the value? Bug?
$name1 = 4
$name2 = 4
$name3 = 4
$myObject = New-Object System.Object
$myObject | Add-Member -type NoteProperty -name name1 -Value [int]$name1
$myObject | Add-Member -type NoteProperty -name name2 -Value $($name2 -as [int])
$myObject | Add-Member -type NoteProperty -name name3 -Value $([int]$name3)
$myObject
Output:
name1 name2 name3
----- ----- -----
[int]4 4 4
Powershell version:
get-host | select-object version
Version
-------
5.1.19041.1023
There's good information in the existing answers, but let me attempt a systematic overview:
tl;dr
In order to pass the output from an expression (e.g. [int] $name1
or $env:HOME + '\foo'
), or (nested) command (e.g. Get-Date -Format yyyy
) as an argument to a command (e.g. Add-Member
), enclose it in (...)
, the grouping operator:
# Note the required (...) around expression [int] $name1
Add-Member -type NoteProperty -name name1 -Value ([int] $name1)
By contrast, $(...)
, the subexpression operator is typically not needed in this scenario, and its use can have side effects - see this answer.
"..."
), you only ever need $(...)
to either enclose a language statement (such as an if
statement or foreach
loop) or multiple statements (any mix of commands, expressions, and language statements separated with ;
).PowerShell has two fundamental parsing modes:
argument mode, which works like shells.
.ps1
script), followed by a whitespace-separated list of arguments, where strings may be unquoted[1] and arguments composed of a mix of literal parts and variable references are typically treated like expandable strings (as if they were enclosed in "..."
).expression mode, which works like programming languages, where strings must be quoted, and operators and language statements such as assignments, foreach
and while
loops, casts can be used.
The conceptual about_Parsing
provides an introduction to these modes; in short, it is the first token in a given context that determines which mode is applied.
A given statement may be composed of parts that are parsed in either mode, which is indeed what happens above:
Because your statement starts with a command name (Add-Member
), it is parsed in argument mode.
(...)
forces a new parsing context, which in the case at hand ([int] $name1
) is parsed in expression mode, due to starting with [
).
What is considered a metacharacter (a character with special, syntactic meaning) differs between the parsing modes:
[
and =
are special only in expression mode, not in argument mode, where they are used verbatim.
Conversely, a token-initial @
followed by a variable name is only special in argument mode, where it is used for parameter splatting.
Compound argument [int]$name1
is therefore treated as if it were an expandable string, and results in verbatim string [int]4
.
Some expressions do not require enclosing in (...)
when used as command arguments (assume $var = 'Foo'
):
Write-Output $var
or Write-Output $env:OS
)Write-Output $var.Length
)Write-Output $var.ToUpper()
)Note that these arguments are passed with their original data type, not stringified (although stringification may be performed by the receiving command).
Pitfalls:
You sometimes need to use "..."
explicitly in order to suppress property-access interpretation and have a .
following a variable reference be interpreted verbatim (e.g. Write-Output "$var.txt"
in order to get verbatim foo.txt
).
If you use $(...)
as part of a compound argument without explicit "..."
quoting, that argument is broken into multiple arguments if the $(...)
subexpression starts the argument (e.g., Write-Output $('a' + 'b')/c
passes two arguments, verbatim ab
and /c
, whereas Write-Output c/$('a' + 'b')
passed just one, verbatim c/ab
).
Similarly, mixing quoted and unquoted strings to form a single argument only works if the argument starts with an unquoted token (e.g., Write-Output One"$var"'$Two'
works as expected and yields verbatim OneFoo$Two
, but Write-Output 'One'"$var"'$Two'
is passed as three arguments, verbatim One
, Foo
, and $Two
).
In short:
The exact rules for how arguments are parsed are complex:
To be safe, avoid use of $(...)
outside "..."
and avoid mixing quoting styles in a single string argument; either use a (single) "..."
string (e.g. Write-Output "$(Split-Path $PROFILE)/foo.txt"
or ) or string concatenation in an expression (Write-Output ('One' + $var + '$Two')
[1] Assuming they contain neither spaces nor any of PowerShell's metacharacters (see this answer). While quoting typically takes the form of enclosing the entire argument in single or double-quotes, as appropriate (e.g. 'foo bar'
, "foo $var"
), it is also possible to quote (escape) individual characters (e.g. foo` bar
), using the backtick (`
), PowerShell's escape character.