PowerShell: Get-Item vs Get-ChildItem output types

1.7k Views Asked by At

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.

1

There are 1 best solutions below

0
On BEST ANSWER

tl;dr:

  • Get-Item isn't worth using with the env: drive's root; to enumerate the current process' environment variables, use Get-ChildItem env: instead.

  • Generally, use Get-Item to get information about the targeted item itself, and Get-ChildItem to get information about its children.


Get-Item is designed to return a given item itself, whereas Get-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 to Get-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 as C:\, 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 that Get-Item env: outputs:

# Currently the same as: Get-ChildItem env: | Sort-Object Name
(Get-Item env:) | Sort-Object Name

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 by Get-Item env:

(Get-Item env:).Name  # returns array of all env.-var. *names*; same as .Key 

(Get-Item env:).Value  # returns array of all *values*

"Sloppy" use of Get-ChildItem

As stated, for item types that by definition cannot have child items, Get-ChildItem falls back to Get-Item behavior, so that the following two commands are effectively equivalent:

Get-Item env:Path

# Same, because an environment variable can never have children,
# but it's better to use Get-Item.
Get-ChildItem env:Path

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 of
Get-Content env:PATH (not Get-Item).


[1] Get-Item env: returns the .Values property value of the System.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 with Get-Item env: | Get-Member