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.