Note: This answer originally incorrectly claimed that $table.6
doesn't work, due to the hashtable key being integer-typed, but it works just fine, because the 6
in the .6
"property" access is parsed as an integer as well.
As Gyula Kokas' helpful answer points out, you're seeing problematic behavior in PowerShell's parser, present as of PowerShell 7.2, discussed in detail in GitHub issue #14036. The behavior is unrelated to hashtables per se:
What follows .
, the member-access operator, is parsed as a number if it looks like a number literal, such as 6
, 6l
(a [long]
) or even 6.0
(!, a [double]
).
- See below for how that number is then used.
Any attempt to use another property access then triggers the error you saw:
$foo.6.bar # !! Error "Missing property name after reference operator."
As an aside: PowerShell even allows you to use variable references, and even expressions (enclosed in (...)
) as property names (e.g., $propName = 'Length'; 'foo'.$propName
or ('foo'.('L' + 'ength')
Workarounds:
As you've demonstrated, enclosing the first property access in (...)
- ($table.6).Count
works, and so does $table.(6).Count
You've also demonstrated $table[6].Count
, which works for hashtables.
For accessing an object's actual property, $obj.'6'.Count
would work too (given that property names are always strings), as it would with hashtables with string keys (e.g. @{ '6'= 'six' }.'6'.Length
)
Considerations specific to hashtables:
As a syntactic convenience, PowerShell allows property-access syntax ($obj.someProperty
) to also be used to access the entries of a hashtable, in which case the property "name" is taken as the key of an entry in the hashtable.
The type-native syntax to access hashtable entries is index syntax ($hash[$someKey]
).
While property names (referring to members of a .NET type) are invariably strings:
- a hashtable's keys can be of any type.
- on accessing an entry, the lookup key must not only have the right value, but must also be of the exact same type as the key stored in the hashtable.
Perhaps surprisingly, when defining a hashtable, unquoted keys situationally become either strings or numbers:
If the unquoted word can be interpreted as a number literal (e.g., 6
), it is parsed as such, and you end up with a numeric key; in the case of 6
, it will be [int]
-typed key, because the usual number-literal typing applies (e.g., 1.0
would become a [double]
key (not advisable, because binary floating-point values do not always have exact decimal representations), and 2147483648
would become a [long]
).
Otherwise (e.g., NameServers
), the key is [string]
-typed.
- Caveat: If the name starts with a digit (but isn't a number literal; e.g.
6a
), an error occurs; use quoting ('6a'
) as a workaround - see GitHub issue #15925
That is, even though strings in expressions normally require quoting, for convenience you may omit the quoting when defining keys in hashtable literals - but the rules for recognizing number literals still apply.
Explicitly typing hashtable keys:
To ensure that a given key is interpreted as a [string]
, quote it (e.g., '6'
)
Generally, you may also use a cast to type your keys explicitly. (e.g. [long] 6
) or, in number literals, the usual number-type suffix characters, such as L
for [long]
(e.g. 6L
) - see this answer for an overview of the supported suffixes, whose number has grown significantly in PowerShell (Core) 7+.
An example, based on your hashtable:
It follows from the above that your 6
key is of type [int]
(and would have to be defined as '6'
in your hashtable literal if you wanted it to be a string).
Because the 6
in $name.6
is also parsed as an [int]
, the lookup succeeds, but note that it wouldn't succeed if different numeric types were at play:
# !! Output is $null, because the entry key is of type [long],
# !! whereas the lookup key is [int].
@{ 6L = '[long] 6' }.6
Considerations specific to property access:
With actual property access (accessing a native member of a .NET type), the fact that names that look like numbers are actually parsed as numbers first - before, of necessity, being converted to strings - can result in surprising behavior:
# !! Output is $null, because the 6L after "." is parsed as a
# !! ([long]) number first, and then converted to a string, which
# !! results in "6" being used.
([pscustomobject] @{ '6L' = 'foo' }).6L
# OK - The quoting forces 6L to be a string.
([pscustomobject] @{ '6L' = 'foo' }).'6L' # -> 'foo'