powershellpowershell-5.0pscustomobject

Why is PSCustomObject being populated with individual characters instead of a single string?


I am trying to learn PowerShell by translating an old batch script that I made for converting videos using FFmpeg.

That has almost nothing to do with the issue at hand, I believe.

This is the code snippet giving me trouble:

[string]$FileList              = (Get-Clipboard).Split("`n")
[int]$Counter               = 0

$List                  = @(ForEach ($i in $FileList)
{
    [PSCustomObject]
   @{
        VideoHeight = (ffprobe.exe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 "$i")
        VideoDuration = (ffprobe.exe -v error -select_streams v:0 -show_entries stream=duration -of csv=s=x:p=0 "$i")
    }
    $Counter++
})

Store in $FileList the list of files from the Clipboard separated by a new line.

Store in $Counter the integer 0.

For each item in $FileList, create a new object containing the item's height and duration and add 1 to $Counter.

Seems straight-forward, right? Here's the catch: if there is only one file with a height of 1080 in $FileList, $List.VideoHeight[0] will return only 1 but if there is at least two files in $FileList, $List.VideoHeight[0] will return 1080.

Command line output:

Single File
$List.VideoHeight:
1080
$List.VideoHeight[0]:
1
Multiple Files
$List.VideoHeight:
1080
1080
720
$List.VideoHeight[0]:
1080

Any ideas what is happening here? I am stuck.


Solution

  • Use $List[0].VideoHeight, not $List.VideoHeight[0].

    After all, from a conceptual standpoint, you want to get the .VideoHeight value of the first list item ($List[0]), not the first element of the whole list's video-height values ($List.VideoHeight).[1]


    The reason it works with multiple items in $List is that PowerShell's member-access enumeration then returns an array of .VideoHeight property values, where indexing with [0] works as expected.

    The reason it doesn't work as expected with a single item in $List is that then only a scalar (single) .VideoHeight property value is returned, and that scalar is of type [string]. Indexing into a single string returns the individual characters in the string, which is what you saw.

    Simple demonstration:

    PS> ([pscustomobject] @{ VideoHeight = '1080' }).VideoHeight[0]
    1  # [char] '1', the first character in string '1080'
    

    vs.

    PS> ([pscustomobject] @{ VideoHeight = '1080' }, 
         [pscustomobject] @{ VideoHeight = '1081' }).VideoHeight[0]
    1080  # [string] '1080', the first element in array '1080', '1081'
    

    So there are two factors contributing to the unexpected behavior:


    [1] If collecting the values of all $List .VideoHeight property values consistently returned an array (why it doesn't is explained in the second section), the two statements would be functionally equivalent, though $List[0].VideoHeight is still preferable for efficiency (no intermediate array of property values must be constructed) and also for the sake of avoiding potential member-name conflicts, which member-access enumeration inherently entails - see this GitHub issue.