The differences ultimately stem from PowerShell's two fundamental parsing modes, explained in this answer; in short:
argument mode is shell-like, for invoking commands with whitespace-separated arguments, with support for unquoted strings.
expression mode works like a traditional programming language, with quoted strings, operators, loop constructs, ...
In both your examples, commands are invoked (more specifically, cmdlets, namely Where-Object
(whose built-in aliases are ?
and where
) and Select-Object
(whose built-in alias is select
), so what is being passed to them is parsed in argument mode:
Where-Object IPv4DefaultGateWay -NE $null
uses argument mode for simplified syntax, i.e. a lower-ceremony argument-mode alternative to an expression-mode script block ({ ... }
), also available with the ForEach-Object
cmdlet.
The syntax is simpler:
- No need for enclosure in
{ ... }
- No need to refer to the input object at hand via the automatic
$_
variable, just using the property name by itself is enough.
But it is limited:
- You can only perform a single operation, on a single property.[1]
- That property must be an immediate property (e.g.,
IPv4DefaultGateWay
, not a nested one (e.g. NetAdapter.Status
) - more on that below.
Select-Object
too has the constraint that a given property name must be an immediate property.
- The only way to work around that is via a calculated property, implemented via a hashtable whose
Expression
entry contains a script block that calculates the property value for each input object - see this answer.
Why nested property access (e.g., NetAdapter.Status
) isn't supported in argument-parsing mode:
An argument such as NetAdapter.Status
- whether quoted or not - is passed as a string to commands, and Where-Object
and Select-Object
interpret such a string passed to their -Property
parameter as the verbatim name of a property, not as a nested property-access expression.
That is, the equivalent of the following command, where NetAdapter.Status
is parsed in argument mode:
Get-NetIPConfiguration -Detailed |
Select-Object -ExpandProperty NetAdapter.Status
is the following expression-mode property access:
Get-NetIPConfiguration -Detailed |
ForEach-Object { $_.'NetAdapter.Status' }
Note the '...'
around NetAdapter.Status
, showing that this name was used verbatim as the single and immediate property name on the input object (which doesn't work as intended).
Design rationale:
The challenge is that in argument mode no distinction is made between NetAdapter.Status
and 'NetAdapter.Status'
and "NetAdapter.Status"
- the target command sees verbatim NetAdapter.Status
in all cases, so - unlike in expression mode - the original quoting / non-quoting cannot serve to distinguish these argument forms.
However, arguably it is much more useful for cmdlets that specifically accept property names (parameter -Property
) to interpret arguments such as NetAdapter.Status
as nested property accessor, given that property names with embedded .
chars. are rare.
Changing this behavior would be a breaking change, however, given that the following currently works, but wouldn't any longer:
PS> '{ "foo.bar": 42 }' | ConvertFrom-Json | Select-Object foo.bar
foo.bar
-------
42
[1] The two parsing modes are so different that it would be impossible to recreate all expression-mode features in argument mode; you couldn't model the complexities of expression mode with command arguments (parameter definitions). Simplified syntax is a compromise aimed at making simple, but common use cases syntactically easier.