I was trying to print out environment variables in alphabetical order (by name), and the first thing I could come up with was:
Get-Item env: | Sort-Object -Property Name
But the output was always unsorted. Then I tried the following
Get-ChildItem env: | Sort-Object -Property Name
And got the expected, correctly sorted, output. This suggests that the output of Get-Item
and Get-ChildItem
is not of same type, even though the (formatted) output from Get-Item env:
and Get-ChildItem env:
looks precisely the same (i.e. dictionary)
Piping the output from the commands to | Get-TypeData
revealed that Get-Item env:
seems to actually return just single System.Collections.DictionaryEntry
, whereas Get-ChildItem env:
returns multiple System.Collections.DictionaryEntry
objects.
Can someone explain what is precisely happening here? Why two seemingly different input data types produce identical looking output/string representation? Is PowerShell doing some implicit "unboxing" of the single-entry dictionary object from Get-Item
output?
Using PowerShell 5.1 on Windows 10.
tl;dr:
Get-Item
isn't worth using with theenv:
drive's root; to enumerate the current process' environment variables, useGet-ChildItem env:
instead.Generally, use
Get-Item
to get information about the targeted item itself, andGet-ChildItem
to get information about its children.Get-Item
is designed to return a given item itself, whereasGet-ChildItem
returns its children.Note:
Get-ChildItem
falls back to the item itself for items that by definition cannot have children, such as individual environment variables or files - see bottom section.Get-Item env:
is akin toGet-Item C:\
in that you're asking for the root of a PowerShell drive itself, not its children.env:
is the PowerShell drive that contains all environment variables defined in the current process, and as itself it currently has a representation of limited utility, only accessing its children works as expected. (Contrast this with a root directory such asC:\
, which has meaningful properties itself, such as timestamps, permissions, ...)What PowerShell returns for the
env:
drive is the collection of entries from the dictionary it uses to store information about the individual environment variables as a single object[1], which is usual behavior, in that commands are normally expected to send a collection's elements to the pipeline, one by one. That information about the item itself effectively includes the children too is unusual as well.It is a moot point, because
Get-ChildItem env:
will give you the same functionality in a conceptually clearer manner, but you could use(...)
, the grouping operator to force enumeration of the items in the collection thatGet-Item env:
outputs:What would arguably more sense if PowerShell returned the whole dictionary rather than its collection of entries, so that you could access
.Keys
to get all environment-variable names and.Values
to get all values. (Dictionaries / hashtables are not expected to be enumerated in PowerShell pipelines).In fact, due to member-access enumeration, you could achieve the same effect by accessing properties
.Key
and.Value
on the entry collection currently returned byGet-Item env:
"Sloppy" use of
Get-ChildItem
As stated, for item types that by definition cannot have child items,
Get-ChildItem
falls back toGet-Item
behavior, so that the following two commands are effectively equivalent:However, it is conceptually preferable to use
Get-Item
in such situations, given that it expresses the intent unambiguously.As an aside: the commonly used
$env:PATH
syntax for directly retrieving a given environment variable's value is an instance of namespace variable notation and is the equivalent ofGet-Content env:PATH
(notGet-Item
).[1]
Get-Item env:
returns the.Values
property value of theSystem.Collections.Generic.Dictionary`2
instance that PowerShell uses to store information about environment variables. That value is output as a single object, and its type is a collection type nested inside the dictionary type,System.Collections.Generic.Dictionary`2.ValueCollection
; you can inspect the type withGet-Item env: | Get-Member