arrayspowershellconstruction

Is there a meaningful difference in this '@()' construction or is this a bug?


I have a function whose name parameter I want to provide tab completion for, the tab completion values are file names found in c:\temp. On top of using the files in c:\temp as values, I also want to add additional tab completion values. Below is the function

Function foo{
    Param(
    [ValidateSet([layoutNames], ErrorMessage = """{0}"" Is not a valid Layout name")]
    $Name
    )
    $name
}

and the layoutNames class, which is used by the name parameter:

Class layoutNames : System.Management.Automation.IValidateSetValuesGenerator{
    [string[]] GetValidValues(){
        #return @((Get-ChildItem -path 'c:\temp' -File).BaseName, "valueFoo", "valueBar")        #tab completetion only suggests "valueFoo" and "valueBar"
        #return @("valueFoo", "valueBar", (Get-ChildItem -path 'c:\temp' -File).BaseName)        #tab completetion only suggests "valueFoo" and "valueBar"
        return @(                                                                                #tab completetion suggests "valueFoo" and "valueBar" and the file names.
                    "valueFoo", "valueBar"
                    (Get-ChildItem -path 'c:\temp' -File).BaseName
                    )
    }}

With the above, only the third return example works, the only difference being a new line....I think.

I spent quite some time trying to figure this out, I initially started with a return statement that looked like this:

return [string[]]("valueFoo", "valueBar", (Get-ChildItem -path 'c:\temp' -File).BaseName)

but kept changing it around as nothing was working, until I eventually worked by way to using the array operator @().

The crux of my issue is that why does the class, when declared in the following manner not work as intended with the foo function, that is suggest both valueFoo, valueBar and the files names in c:\temp

Class layoutNames : System.Management.Automation.IValidateSetValuesGenerator{
    [string[]] GetValidValues(){
        #return [string[]]("valueFoo", "valueBar",(Get-ChildItem -path 'C:\Users\INDESK\AppData\Roaming\GPSoftware\Directory Opus\Layouts' -File).BaseName)             # no files names are suggested. only 'valueFoo' and 'valueBar' are suggested
        #return [string[]]("valueFoo", "valueBar",((Get-ChildItem -path 'C:\Users\INDESK\AppData\Roaming\GPSoftware\Directory Opus\Layouts' -File).BaseName))               # no files names are suggested. only 'valueFoo' and 'valueBar' are suggested
        return [string[]](((Get-ChildItem -path 'C:\Users\INDESK\AppData\Roaming\GPSoftware\Directory Opus\Layouts' -File).BaseName),"valueFoo", "valueBar")                # no files names are suggested. only 'valueFoo' and 'valueBar' are suggested
    }}

Am on pwsh 7.4/win11


Solution

  • Reason why the third example works is because there is no comma before the Get-ChildItem expression. See comma operator , details:

    In expression mode, as a unary operator, the comma creates an array with just one member.

    $arr = @( , (0..10))
    $arr[0]
    
    # 0
    # 1
    # 2
    # etc
    

    It's like doing:

    $item = [object[]]::new(1)
    $item[0] = 0..10
    $arr = @($item)
    $arr[0]
    
    # 0
    # 1
    # 2
    # etc
    

    If you put all expressions in a single line then you're essentially risking creating a jagged array when Get-ChildItem outputs 2 or more items:

    $arr = @(
        'valueFoo', 'valueBar', (0..2)
    )
    $arr[2]
    
    # 0
    # 1
    # 2
    

    And if it returns no items then you would end up with a null element:

    $arr = @(
        'valueFoo', 'valueBar', (& { })
    )
    $arr.Count # 3 (Index 2 is `$null`)
    

    If you're using the Array subexpression operator @( ) and want to put all elements in a single line, then the best you can do is to use ; as your item separator (which symbolizes a new line / new statement):

    $arr = @(
        'valueFoo'; 'valueBar'; (0..2)
    )
    $arr.Count # 5 (the third expression was enumerated)
    
    $arr = @(
        'valueFoo'; 'valueBar'; (& { })
    )
    $arr.Count # 2 (`AutomationNull.Value` didn't count as array element)