openlang

My function returns an array when all I asked for was a path

Writing a PowerShell function from scratch for once (rather than squinting at someone else's), and it misbehaves in a way I can't explain. It's meant to hand back one path. It hands back an array.

function Get-ReportPath {
    $names = [System.Collections.ArrayList]::new()
    foreach ($f in Get-ChildItem -Filter *.xlsx) {
        $names.Add($f.Name)
    }
    Write-Verbose "found $($names.Count) workbooks"
    Join-Path $PWD 'report.xlsx'
}

$path = Get-ReportPath
$path.GetType().Name   # Object[]

With three workbooks in the folder, $path holds four things. The last is the path I wanted. The first three are 0, 1 and 2 (which looked like a counter I never wrote, and I stared at it for a good while). Where do those numbers come from, and what's the usual way to stop a helper list leaking into the result?

The conversation continues 4

Follow the reasoning
  1. Permalink

    Every bare expression goes to output, so be explicit. Write return (Join-Path $PWD 'report.xlsx') and that becomes the result, the stray output before it gets dropped.

  2. Permalink

    Sadly no, and this one surprises everybody once. return in PowerShell just emits its value and leaves the function. Anything already written to the output stays there. The leak is ArrayList.Add, which returns the index of the new item, and a return value nobody captures is output like anything else. That's where the 0, 1, 2 come from.

    Throw the index away at the call. Any of these works.

    [void]$names.Add($f.Name)
    $null = $names.Add($f.Name)
    $names.Add($f.Name) | Out-Null

    (The about_Return help page has nearly the same example, with a "Please wait" string that ends up in the variable next to the result.) Keeping return for readability is fine, it just doesn't clean anything up.

  3. Permalink

    Disagree that the call site is where to fix it. Three casts in three loops and somebody forgets the fourth. Fair that [void] is the quickest patch for this one function. But the list type is the real problem, and [System.Collections.Generic.List[string]]::new() has an Add that returns nothing, so there's no index to leak in the first place.

  4. Permalink

    That was it, thanks both. Swapped in the generic List[string] and $path is a single string again (and Test-Path stopped handing me a whole column of answers, which I'd been politely ignoring).

Other sides of the same question

  1. A hung test reads better from its dump than from prints

    When a test used to hang, I'd sprinkle fmt.Println around and run it again. These days I do something else. I set a short tim…

    Replies 9go
  2. My init set fill-column for exactly one buffer

    Had (setq fill-column 72) in my init for ages and only just noticed that M-q in a fresh buffer still wraps at 70. (In *scratc…

  3. SilentlyContinue is On Error Resume Next with better spelling

    Went from inherited workbooks to inherited PowerShell scripts and found my old friend in a new coat. -ErrorAction SilentlyCon…