PowerShell live documentation

PSCustomObject

Collection-like behavior

Permissive mode

When not running in strict mode, most objects in PowerShell have a Count property:

  1. $array = @(1, 2, 3)
  2. Write-Output "Array Count: $($array.Count)"
  3. $dictionary = @{A=1; B=2; C=3; D=4}
  4. Write-Output "Dictionary Count: $($dictionary.Count)"
  5. $number = 5
  6. Write-Output "Number Count: $($number.Count)"
  7. $string = 'hello there'
  8. Write-Output "String Count: $($string.Count)"
  9. $process = (Get-Process)[0]
  10. Write-Output "Process object Count: $($process.Count)"
Stdout
Array Count: 3
Dictionary Count: 4
Number Count: 
String Count: 
Process object Count: 
Array Count: 3
Dictionary Count: 4
Number Count: 1
String Count: 1
Process object Count: 1

Notice that all these objects have a Count (except for non-collections in version 2). However, PSCustomObject doesn't have an accurate Count of 1 until version 6.1. In version 2, it has the same count as the dictionary it was created out of.

  1. $object = [PSCustomObject]@{A=1; B=2; C=3; D=4; E=5}
  2. Write-Output "PSCustomObject Count: $($object.Count)"
Stdout
PSCustomObject Count: 5
PSCustomObject Count: 
PSCustomObject Count: 1

Basically, PSCustomObject sometimes doesn't act like a collection when other single types do until version 6.1.

We can also look at other collection invocations. The Length property behaves the same as Count in that most objects act like collections, but PSCustomObject doesn't start acting like a collection until version 6.1. There's just the one difference that in version 2, Length on PSCustomObject doesn't return the number of properties in the object like with Count.

Naturally, other types like strings also behave differently with Length, but we're just interested in PSCustomObject here.

  1. $array = @(1, 2, 3)
  2. Write-Output "Array Length: $($array.Length)"
  3. $dictionary = @{A=1; B=2; C=3; D=4}
  4. Write-Output "Dictionary Length: $($dictionary.Length)"
  5. $number = 5
  6. Write-Output "Number Length: $($number.Length)"
  7. $string = 'hello there'
  8. Write-Output "String Length: $($string.Length)"
  9. $process = (Get-Process)[0]
  10. Write-Output "Process object Length: $($process.Length)"
  11. $object = [PSCustomObject]@{A=1; B=2; C=3; D=4; E=5}
  12. Write-Output "PSCustomObject Length: $($object.Length)"
Stdout
Array Length: 3
Dictionary Length: 
Number Length: 
String Length: 11
Process object Length: 
PSCustomObject Length: 
Array Length: 3
Dictionary Length: 1
Number Length: 1
String Length: 11
Process object Length: 1
PSCustomObject Length: 
Array Length: 3
Dictionary Length: 1
Number Length: 1
String Length: 11
Process object Length: 1
PSCustomObject Length: 1

The ForEach method behaves similarly to Count and Length except it throws rather than returning null. Also note that dictionaries behave like singular objects with ForEach.

  1. try {
  2.     $array = @(1, 2, 3)
  3.     $i = -1
  4.     Write-Output "Array ForEach: $($array.ForEach({$i += 1; $i}))"
  5. } catch {
  6.     Write-Output "Array ForEach threw: $_"
  7. }
  8. try {
  9.     $dictionary = @{A=1; B=2; C=3; D=4}
  10.     $i = -1
  11.     Write-Output "Dictionary ForEach: $($dictionary.ForEach({$i += 1; $i}))"
  12. } catch {
  13.     Write-Output "Dictionary ForEach threw: $_"
  14. }
  15. try {
  16.     $number = 5
  17.     $i = -1
  18.     Write-Output "Number ForEach: $($number.ForEach({$i += 1; $i}))"
  19. } catch {
  20.     Write-Output "Number ForEach threw: $_"
  21. }
  22. try {
  23.     $string = 'hello there'
  24.     $i = -1
  25.     Write-Output "String ForEach: $($string.ForEach({$i += 1; $i}))"
  26. } catch {
  27.     Write-Output "String ForEach threw: $_"
  28. }
  29. try {
  30.     $process = (Get-Process)[0]
  31.     $i = -1
  32.     Write-Output "Process object ForEach: $($process.ForEach({$i += 1; $i}))"
  33. } catch {
  34.     Write-Output "Process object ForEach threw: $_"
  35. }
  36. try {
  37.     $object = [PSCustomObject]@{A=1; B=2; C=3; D=4; E=5}
  38.     $i = -1
  39.     Write-Output "PSCustomObject ForEach: $($object.ForEach({$i += 1; $i}))"
  40. } catch {
  41.     Write-Output "PSCustomObject object ForEach threw: $_"
  42. }
Stdout
Array ForEach threw: Method invocation failed because [System.Object[]] doesn't contain a method named 'ForEach'.
Dictionary ForEach threw: Method invocation failed because [System.Collections.Hashtable] doesn't contain a method name
d 'ForEach'.
Number ForEach threw: Method invocation failed because [System.Int32] doesn't contain a method named 'ForEach'.
String ForEach threw: Method invocation failed because [System.String] doesn't contain a method named 'ForEach'.
Process object ForEach threw: Method invocation failed because [System.Diagnostics.Process] doesn't contain a method na
med 'ForEach'.
PSCustomObject object ForEach threw: Method invocation failed because [System.Collections.Hashtable] doesn't contain a 
method named 'ForEach'.
Array ForEach: 0 1 2
Dictionary ForEach: 0
Number ForEach: 0
String ForEach: 0
Process object ForEach: 0
PSCustomObject object ForEach threw: Method invocation failed because [System.Management.Automation.PSCustomObject] does not contain a method named 'ForEach'.
Array ForEach: 0 1 2
Dictionary ForEach: 0
Number ForEach: 0
String ForEach: 0
Process object ForEach: 0
PSCustomObject ForEach: 0

Zero indexing, interestingly, works in all versions 5 through 7, whether number, process object, or PSCustomObject. In version 2, it returns null on PSCustomObject rather than throwing like with numbers and process objects. This matches the behavior that the PSCustomObject cast in version 2 really just produces a dictionary.

  1. try {
  2.     $array = @(1, 2, 3)
  3.     Write-Output "Array [0]: $($array[0])"
  4. } catch {
  5.     Write-Output "Array [0] threw: $_"
  6. }
  7. try {
  8.     $dictionary = @{A=1; B=2; C=3; D=4}
  9.     Write-Output "Dictionary [0]: $($dictionary[0])"
  10. } catch {
  11.     Write-Output "Dictionary [0] threw: $_"
  12. }
  13. try {
  14.     $number = 5
  15.     Write-Output "Number [0]: $($number[0])"
  16. } catch {
  17.     Write-Output "Number [0] threw: $_"
  18. }
  19. try {
  20.     $string = 'hello there'
  21.     Write-Output "String [0]: $($string[0])"
  22. } catch {
  23.     Write-Output "String [0] threw: $_"
  24. }
  25. try {
  26.     $process = (Get-Process)[0]
  27.     Write-Output "Process object [0]: $($process[0])"
  28. } catch {
  29.     Write-Output "Process object [0] threw: $_"
  30. }
  31. try {
  32.     $object = [PSCustomObject]@{A=1; B=2; C=3; D=4; E=5}
  33.     Write-Output "PSCustomObject [0]: $($object[0])"
  34. } catch {
  35.     Write-Output "PSCustomObject [0] threw: $_"
  36. }
Stdout
Array [0]: 1
Dictionary [0]: 
Number [0] threw: Unable to index into an object of type System.Int32.
String [0]: h
Process object [0] threw: Unable to index into an object of type System.Diagnostics.Process.
PSCustomObject [0]: 
Array [0]: 1
Dictionary [0]: 
Number [0]: 5
String [0]: h
Process object [0]: System.Diagnostics.Process (conhost)
PSCustomObject [0]: @{A=1; B=2; C=3; D=4; E=5}

Strict mode

If we enable strict mode, we get some more differing behavior between versions.

In version 2, we fail to get the count on the number, string, and process objects. Everything else is the same. In versions 5 and 6.0, we throw getting the count on everything but the actual collections. Like when not in strict mode, we only start getting Count on PSCustomObject starting in version 6.1. This is interesting, however, because PSCustomObject is now the only non-collection type which doesn't throw in strict mode.

  1. Set-StrictMode -Version Latest
  2. try {
  3.     $array = @(1, 2, 3)
  4.     Write-Output "Array Count: $($array.Count)"
  5. } catch {
  6.     Write-Output "Array Count threw: $_"
  7. }
  8. try {
  9.     $dictionary = @{A=1; B=2; C=3; D=4}
  10.     Write-Output "Dictionary Count: $($dictionary.Count)"
  11. } catch {
  12.     Write-Output "Dictionary Count threw: $_"
  13. }
  14. try {
  15.     $number = 5
  16.     Write-Output "Number Count: $($number.Count)"
  17. } catch {
  18.     Write-Output "Number Count threw: $_"
  19. }
  20. try {
  21.     $string = 'hello there'
  22.     Write-Output "String Count: $($string.Count)"
  23. } catch {
  24.     Write-Output "String Count threw: $_"
  25. }
  26. try {
  27.     $process = (Get-Process)[0]
  28.     Write-Output "Process object Count: $($process.Count)"
  29. } catch {
  30.     Write-Output "Process object Count threw: $_"
  31. }
  32. try {
  33.     $object = [PSCustomObject]@{A=1; B=2; C=3; D=4; E=5}
  34.     Write-Output "PSCustomObject Count: $($object.Count)"
  35. } catch {
  36.     Write-Output "PSCustomObject Count threw: $_"
  37. }
Stdout
Array Count: 3
Dictionary Count: 4
Number Count threw: Property 'Count' cannot be found on this object. Make sure that it exists.
String Count threw: Property 'Count' cannot be found on this object. Make sure that it exists.
Process object Count threw: Property 'Count' cannot be found on this object. Make sure that it exists.
PSCustomObject Count: 5
Array Count: 3
Dictionary Count: 4
Number Count threw: The property 'Count' cannot be found on this object. Verify that the property exists.
String Count threw: The property 'Count' cannot be found on this object. Verify that the property exists.
Process object Count threw: The property 'Count' cannot be found on this object. Verify that the property exists.
PSCustomObject Count threw: The property 'Count' cannot be found on this object. Verify that the property exists.
Array Count: 3
Dictionary Count: 4
Number Count threw: The property 'Count' cannot be found on this object. Verify that the property exists.
String Count threw: The property 'Count' cannot be found on this object. Verify that the property exists.
Process object Count threw: The property 'Count' cannot be found on this object. Verify that the property exists.
PSCustomObject Count: 1

We see something similar with the Length property. Up until version 6.1, all types but arrays and strings throw. However, once we reach 6.1, PSCustomObject now has a Length property, even in strict mode.

  1. Set-StrictMode -Version Latest
  2. try {
  3.     $array = @(1, 2, 3)
  4.     Write-Output "Array Length: $($array.Length)"
  5. } catch {
  6.     Write-Output "Array Length threw: $_"
  7. }
  8. try {
  9.     $dictionary = @{A=1; B=2; C=3; D=4}
  10.     Write-Output "Dictionary Length: $($dictionary.Length)"
  11. } catch {
  12.     Write-Output "Dictionary Length threw: $_"
  13. }
  14. try {
  15.     $number = 5
  16.     Write-Output "Number Length: $($number.Length)"
  17. } catch {
  18.     Write-Output "Number Length threw: $_"
  19. }
  20. try {
  21.     $string = 'hello there'
  22.     Write-Output "String Length: $($string.Length)"
  23. } catch {
  24.     Write-Output "String Length threw: $_"
  25. }
  26. try {
  27.     $process = (Get-Process)[0]
  28.     Write-Output "Process object Length: $($process.Length)"
  29. } catch {
  30.     Write-Output "Process object Length threw: $_"
  31. }
  32. try {
  33.     $object = [PSCustomObject]@{A=1; B=2; C=3; D=4; E=5}
  34.     Write-Output "PSCustomObject Length: $($object.Length)"
  35. } catch {
  36.     Write-Output "PSCustomObject Length threw: $_"
  37. }
Stdout
Array Length: 3
Dictionary Length threw: Property 'Length' cannot be found on this object. Make sure that it exists.
Number Length threw: Property 'Length' cannot be found on this object. Make sure that it exists.
String Length: 11
Process object Length threw: Property 'Length' cannot be found on this object. Make sure that it exists.
PSCustomObject Length threw: Property 'Length' cannot be found on this object. Make sure that it exists.
Array Length: 3
Dictionary Length threw: The property 'Length' cannot be found on this object. Verify that the property exists.
Number Length threw: The property 'Length' cannot be found on this object. Verify that the property exists.
String Length: 11
Process object Length threw: The property 'Length' cannot be found on this object. Verify that the property exists.
PSCustomObject Length threw: The property 'Length' cannot be found on this object. Verify that the property exists.
Array Length: 3
Dictionary Length threw: The property 'Length' cannot be found on this object. Verify that the property exists.
Number Length threw: The property 'Length' cannot be found on this object. Verify that the property exists.
String Length: 11
Process object Length threw: The property 'Length' cannot be found on this object. Verify that the property exists.
PSCustomObject Length: 1

ForEach works on all types in versions 5 and 6.0 in strict mode, except for PSCustomObject. In version 6.1 it works for PSCustomObject as well. This differs from Count and Length, in that supporting ForEach on PSCustomObject in strict mode actually produces consistency with other types.

  1. Set-StrictMode -Version Latest
  2. try {
  3.     $array = @(1, 2, 3)
  4.     $i = -1
  5.     Write-Output "Array ForEach: $($array.ForEach({$i += 1; $i}))"
  6. } catch {
  7.     Write-Output "Array ForEach threw: $_"
  8. }
  9. try {
  10.     $dictionary = @{A=1; B=2; C=3; D=4}
  11.     $i = -1
  12.     Write-Output "Dictionary ForEach: $($dictionary.ForEach({$i += 1; $i}))"
  13. } catch {
  14.     Write-Output "Dictionary ForEach threw: $_"
  15. }
  16. try {
  17.     $number = 5
  18.     $i = -1
  19.     Write-Output "Number ForEach: $($number.ForEach({$i += 1; $i}))"
  20. } catch {
  21.     Write-Output "Number ForEach threw: $_"
  22. }
  23. try {
  24.     $string = 'hello there'
  25.     $i = -1
  26.     Write-Output "String ForEach: $($string.ForEach({$i += 1; $i}))"
  27. } catch {
  28.     Write-Output "String ForEach threw: $_"
  29. }
  30. try {
  31.     $process = (Get-Process)[0]
  32.     $i = -1
  33.     Write-Output "Process object ForEach: $($process.ForEach({$i += 1; $i}))"
  34. } catch {
  35.     Write-Output "Process object ForEach threw: $_"
  36. }
  37. try {
  38.     $object = [PSCustomObject]@{A=1; B=2; C=3; D=4; E=5}
  39.     $i = -1
  40.     Write-Output "PSCustomObject ForEach: $($object.ForEach({$i += 1; $i}))"
  41. } catch {
  42.     Write-Output "PSCustomObject ForEach threw: $_"
  43. }
Stdout
Array ForEach threw: Method invocation failed because [System.Object[]] doesn't contain a method named 'ForEach'.
Dictionary ForEach threw: Method invocation failed because [System.Collections.Hashtable] doesn't contain a method name
d 'ForEach'.
Number ForEach threw: Method invocation failed because [System.Int32] doesn't contain a method named 'ForEach'.
String ForEach threw: Method invocation failed because [System.String] doesn't contain a method named 'ForEach'.
Process object ForEach threw: Method invocation failed because [System.Diagnostics.Process] doesn't contain a method na
med 'ForEach'.
PSCustomObject ForEach threw: Method invocation failed because [System.Collections.Hashtable] doesn't contain a method 
named 'ForEach'.
Array ForEach: 0 1 2
Dictionary ForEach: 0
Number ForEach: 0
String ForEach: 0
Process object ForEach: 0
PSCustomObject ForEach threw: Method invocation failed because [System.Management.Automation.PSCustomObject] does not contain a method named 'ForEach'.
Array ForEach: 0 1 2
Dictionary ForEach: 0
Number ForEach: 0
String ForEach: 0
Process object ForEach: 0
PSCustomObject ForEach: 0

Finally, zero indexing works on all types in versions 5 through 7, even in strict mode.

  1. Set-StrictMode -Version Latest
  2. try {
  3.     $array = @(1, 2, 3)
  4.     Write-Output "Array [0]: $($array[0])"
  5. } catch {
  6.     Write-Output "Array [0] threw: $_"
  7. }
  8. try {
  9.     $dictionary = @{A=1; B=2; C=3; D=4}
  10.     Write-Output "Dictionary [0]: $($dictionary[0])"
  11. } catch {
  12.     Write-Output "Dictionary [0] threw: $_"
  13. }
  14. try {
  15.     $number = 5
  16.     Write-Output "Number [0]: $($number[0])"
  17. } catch {
  18.     Write-Output "Number [0] threw: $_"
  19. }
  20. try {
  21.     $string = 'hello there'
  22.     Write-Output "String [0]: $($string[0])"
  23. } catch {
  24.     Write-Output "String [0] threw: $_"
  25. }
  26. try {
  27.     $process = (Get-Process)[0]
  28.     Write-Output "Process object [0]: $($process[0])"
  29. } catch {
  30.     Write-Output "Process object [0] threw: $_"
  31. }
  32. try {
  33.     $object = [PSCustomObject]@{A=1; B=2; C=3; D=4; E=5}
  34.     Write-Output "PSCustomObject [0]: $($object[0])"
  35. } catch {
  36.     Write-Output "PSCustomObject [0] threw: $_"
  37. }
Stdout
Array [0]: 1
Dictionary [0]: 
Number [0] threw: Unable to index into an object of type System.Int32.
String [0]: h
Process object [0] threw: Unable to index into an object of type System.Diagnostics.Process.
PSCustomObject [0]: 
Array [0]: 1
Dictionary [0]: 
Number [0]: 5
String [0]: h
Process object [0]: System.Diagnostics.Process (conhost)
PSCustomObject [0]: @{A=1; B=2; C=3; D=4; E=5}

See also