4

I've got a build script (PowerShell 4 on Windows 2012 R2) that runs NUnit in a background job and returns NUnit's output. This output is collected in a Collections.Generic.List[string].

$nunitJob = Start-Job -ScriptBlock { 
                                    param(
                                        [string]
                                        $BinRoot,

                                        [string]
                                        $NUnitConsolePath,

                                        [string[]]
                                        $NUnitParams,

                                        [string]
                                        $Verbose
                                    )

                                    Set-Location -Path $BinRoot
                                    $VerbosePreference = $Verbose

                                    Write-Verbose -Message ('{0} {1}' -f $NUnitConsolePath,($NUnitParams -join ' '))
                                    & $NUnitConsolePath $NUnitParams 2>&1 
                                    $LASTEXITCODE
                                 } -ArgumentList $binRoot,$nunitConsolePath,$nunitParams,$VerbosePreference

$nunitJob | Wait-Job -Timeout ($timeoutMinutes * 60) | Out-Null
$jobKilled = $false
if( $nunitJob.JobStateInfo.State -eq [Management.Automation.JobState]::Running )
{
    $jobKilled = $true
    $errMsg = 'Killing {0} tests: exceeded {1} minute timeout.' -f $assembly.Name,$timeoutMinutes
    Write-Error -Message $errMsg
}

$output = New-Object 'Collections.Generic.List[string]'

$nunitJob | 
    Stop-Job -PassThru | 
    Receive-Job |
    ForEach-Object  { 
        if( -not $_ )
        {
            [void]$output.Add( '' )
            return
        }

        switch -Regex ( $_ )
        {
            '^Tests run: (\d+), Errors: (\d+), Failures: (\d+), Inconclusive: (\d+), Time: ([\d\.]+) seconds$'
            {
                $testsRun = $Matches[1]
                $errors = $Matches[2]
                $failures = $Matches[3]
                $inconclusive = $Matches[4]
                $duration = New-Object 'TimeSpan' 0,0,$Matches[5]
                break
            }
            '^  Not run: (\d+), Invalid: (\d+), Ignored: (\d+), Skipped: (\d+)$'
            {
                $notRun = $Matches[1]
                $invalid = $Matches[2]
                $ignored = $Matches[3]
                $skipped = $Matches[4]
                break
            }
        }

        # Error happens here:
        [void] $output.Add( $_ )
    }

Intermittently, our build will fail with this error:

Cannot find an overload for "Add" and the argument count: "1".
At line:XXXXX char:XXXXX
+ [void] $output.Add( $_ )
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest

Any idea why PowerShell would not be able to find List[string]'s Add method?

I've opened a console Window and played around with passing different typed objects to Add without getting an error.

> $o = New-Object 'Collections.Generic.List[string]'
> $o.Add( '' )
> $o.Add( $null )
> $o.Add( 1 )
> $o


1
Aaron Jensen
  • 25,861
  • 15
  • 82
  • 91
  • 1
    Problem is most likely that it cannot implicitly convert `$_` to a string. Like `$o.Add($(New-Object psobject))` – Mathias R. Jessen Aug 27 '15 at 23:34
  • @zespri I did remove some lines, but none that involved `$output`. – Aaron Jensen Aug 27 '15 at 23:35
  • 1
    @MathiasR.Jessen That may be it. I tried `$output.Add( [pscustomobject]@{ } )` and am able to reproduce the error. Now I have to figure out why PowerShell isn't treating console output as strings. – Aaron Jensen Aug 27 '15 at 23:38
  • "treating console output as strings"? Do you attempt to modify `$_` in any way during foreach-object? Again, the entire code block might be useful/meaningful here. – Mathias R. Jessen Aug 27 '15 at 23:43
  • @MathiasR.Jessen I just added the code that starts the background NUnit job. Looks like that script block is returning things PowerShell can't convert to a string. Not sure what that could be. – Aaron Jensen Aug 27 '15 at 23:47
  • 3
    It might be worth adding a `Write-Verbose $_.GetType().Fullname -Verbose` just to see what kind of object you are dealing with. – boeprox Aug 28 '15 at 02:21
  • @boeprox Already added. The fix is easy: just explicitly call ToString() on each object added to the list. But before I do, I have to know what kind of object is throwing a kink in the works. – Aaron Jensen Aug 28 '15 at 02:54
  • `switch` has really odd behavior in powershell, it's not a cmdlet. See http://stackoverflow.com/questions/18721593/powershell-set-psdebug-trace-2-causes-unexpected-results – Eris Aug 28 '15 at 06:00
  • @Aaron Jensen Yep, using ToString() will definitely be the easy fix. You could also use `Set-PSBreakPoint` on `[void] $output.Add( $_ )` so you can then inspect the object. – boeprox Aug 28 '15 at 11:38

1 Answers1

1

When you redirect stderr to stdout, you can get System.Management.Automation.ErrorRecords interspersed with strings. Stderr is automatically converted to ErrorRecords while stdout is strings.

So you'll probably want to look to see if the output contains ErrorRecords of interest and if not then filter them out.

Χpẘ
  • 3,403
  • 1
  • 13
  • 22