powershell Get-ChildItem result in array

2.4k views Asked by At

(Get-ChildItem -File -Recurse -Path $path).Fullname returns array of full names (Get-ChildItem -File -Recurse -Path $path).Name returns array of files names but (Get-ChildItem -File -Recurse -Path $path).Length returns only one value - the count of elements

Question - how to get result as array of file lengths ?

3

There are 3 answers

0
Mathias R. Jessen On

The one value you get is indeed the length of the resulting array - the Array class has an explicit Length property that takes precedence over property enumeration.

To get the individual Length property values from each item in the array, pipe to either Select-Object or ForEach-Object, like so:

Get-ChildItem -File -Recurse -Path $path |ForEach-Object -MemberName Length
# or 
Get-ChildItem -File -Recurse -Path $path |Select-Object -ExpandProperty Length
0
Christian Held On

In your example you get the Length of the resulting array.

Get-ChildItem -File returns an array of System.IO.FileInfo. So you need to take (select) the Length attribute of every entry in the array. This is done by using Select-Object or just select:

Get-ChildItem -File -Recurse  -Path $path | Select-Object Length

which would give you an array of objects containing the file's length as Length property. You can also select multiple properties:

Get-ChildItem -File -Recurse  -Path $path | Select-Object Name, Length
0
mklement0 On

To complement Mathias R. Jessen's helpful answer:

tl;dr

Use of the .ForEach() array method enables a concise and performant solution:

# Return the files' .Length property values as a collection.
(Get-ChildItem -File -Recurse -Path $path).ForEach('Length')

As pointed out in Mathias' answer, type-native properties - such as .Length on array instances - take precedence over the so-called member-access enumeration that you tried to perform - that is, you wanted to collect the .Length property values of the elements of your collection (array; the System.IO.FileInfo instances output by Get-ChildItem), and return them as an ([object[]]) array.

  • GitHub issue #7445 discusses this situational ambiguity and proposes introducing a dedicated operator, say %. so that you can unambiguously request either regular property access or member-access enumeration.

A perhaps surprising aspect of member-access enumeration is that it applies pipeline logic in that it situationally returns a scalar, namely if the collection happens to contain just one element.

  • E.g., @(Get-Date).Year.GetType().Name returns an System.Int32, not Object[], indicating that an integer rather than a (single-element) array containing an integer was returned.
  • GitHub issue #6802 discusses this.

Member-access enumeration is not only convenient, but also fast, because it avoids looping / enumeration in PowerShell code.

Use of the ForEach-Object or Select-Object cmdlets, as shown in Mathias' answer, is definitely a functional but slow workaround for not being able to use member-access enumeration in a given situation, due to use of the pipeline, which isn't necessary for input that that is already in memory in full.

Therefore, use of the .ForEach() array method - using the overload where you simply specify the target property name ('Length') - is a concise and better-performing alternative.

  • Note: Unlike with member-access enumeration, the return value from .ForEach() is always a collection, albeit not an array: it is of type [System.Collections.ObjectModel.Collection[psobject]], which, however, will behave like an array in most contexts.[1]

[1] Unlike an array, this type of collection is extensible (you can add or remove elements). Due to the element type being [psobject], each element is typically invisibly wrapped in this type; e.g., 42 -is [psobject] is $false, but @(42).ForEach({ $_ })[0] -is [psobject] is $true. Unfortunately, there are cases where this near-invisible wrapper results in different behavior - see GitHub issue #5579.