Powershell: Write-Error fails to work in class methods that return a non-void value

Created on 3 Nov 2017  路  15Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

Create a class that contains a method with a return value, add a call to Write-Error in the method. Call the method. (see attached script snippet for reproduction)

Expected behavior

The error should be displayed.

Actual behavior

The Write-Error cmdlet does nothing and no error is shown.

Environment data

> $PSVersionTable
Name                           Value
----                           -----
PSVersion                      6.0.0-beta.9
PSEdition                      Core
GitCommitId                    v6.0.0-beta.9
OS                             Microsoft Windows 10.0.15063
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Hacktoberfest Issue-Bug Up-for-Grabs WG-Engine

Most helpful comment

There was no design work around alternate streams in functions defined in classes - the existing behavior is simply a side effect of code reuse.

All 15 comments

@LethiferousMoose Could you please add full repo steps with code samples.

I believe this is by design. PowerShell classes do not leak they either return or Throw

class Test 
{
    Test(){}

    static [string] WriteError()
    {
        Write-Error 'This is an error.'
        return 'String'
    }

    static [string] ThrowError()
    {
        Throw 'This is a throw'
        return 'String'
    }
}

[Test]::WriteError()
[Test]::ThrowError()

result:

String
This is a throw
At line:20 char:9
+         Throw 'This is a throw'
+         ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (This is a throw:String) [], RuntimeException
    + FullyQualifiedErrorId : This is a throw

Write-Error does not "leak" out of the method. it does still populate $Error. You could make errors become terminating errors by setting $ErrorActionPreference

class Test2
{
    Test2() {}

    static [string] WriteTerminatingError()
    {
        $ErrorActionPreference = 'stop'
        Write-Error 'This is a terminating error.'
        return 'String'
    }
}

[Test2]::WriteTerminatingError()

result:

This is a terminating error.
At line:13 char:1
+ [Test2]::WriteTerminatingError()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException

Basically, Class methods either return an object of the specified type or they throw.

I suppose that makes sense, but Write-Error does not terminate by default. It simply writes the error to the error channel and displays it on the console. If I am doing class initiation and I encounter some some of non-terminating error in a method with a return type, I have no way to display that error.

In my scenario, I am loading up a configuration file and populating an object and would like to alert the user if certain configurations will be skipped because they are malformed (in my case a JSON attribute containing environment variables that are not set). When I have a return type of List<> (i.e. the method the populates the list of configurations) I cannot write errors to the console. I had to workaround this by passing the list in as a parameter to the method.

See attached simplified class SampleClass.txt (external custom cmdlets and classes not included.)

In this example this is where the error is written:

if (Test-EnvironmentVariablePath $product.directory) {
    continue
}

Test-EnvironmentVariablePath does some checks and writes an error if a potentially unexpanded environment variable is detected.

Note: this is the modified code using a list as a parameter with a void return signature so that it works.

whoops.. wrong button... sorry. issue reopened.

So why does return type [void] bypass this behavior. If methods are designed to not "leak" why does this have a mixed behavior? Also your reply must have gotten erased when you re-opened this issue.

that is a good question, one for which I do not have an answer.

I just ran into this issue and finally found my way here. Can anybody explain why this behaviour is different when returning [void] vs. e.g. [bool]?
My use case is that I have a function that verifies some files exist before signing them. When everything succeeded, it will return true. However, when the files do not exist, I want to use Write-Error to display which file was missing and then return false to indicate to the calling script that the sign process failed. However, at the moment, I only get "false" back, but no output from Write-Error.

As mentioned above, @mawett, the expected design process when using classes is to throw an exception if an operation in a class method fails. If you have a method like Int32.TryParse() that returns a bool to indicate status you'd typically just return $false and do nothing else.

The problem here, I think, is that classes don't really operate in the PowerShell pipeline like functions do. This means that they're restricted to the same model of output as other .NET classes, which don't have the many streams that PowerShell does.

Perhaps @daxian-dbw can answer why this is not being maintained for [void] methods. I suspect that's actually a _bug_ more than anything.

Ah I see, so it's rather a bug that class methods returning void actually work with Write-Error than the other way around. In that case I will have to clean up some of my Powershell classes :-) Thanks for the clarification

it's rather a bug that class methods returning void actually work with Write-Error

@SteveL-MSFT @daxian-dbw @rjmholt Could you please make a conclusion about the issue? Should we fix this for void class methods (that could be a breaking change)?

Implementation wise, this is because different errorHandlingBehavior is used for method that has a non-void return type and method that has a void return type.
WriteToExternalErrorPipe used in non-void return method vs. WriteToCurrentErrorPipe used in void return method

This feels like an oversight to me. @lzybkr, do you remember if it's intentional?

There was no design work around alternate streams in functions defined in classes - the existing behavior is simply a side effect of code reuse.

See also the discussion in #9702, which is a since-closed effective duplicate.

To summarize:

  • Writing to stream 1 (success output) by design requires return, not Write-Output / implicit output.

  • Writing to 2 by design requires exceptions, not Write-Error.

    • Except in [void] methods, where Write-Error output surfaces, which is an inconsistency that should be fixed.

    • In non-void methods, which function as designed, the implication is that non-terminating errors emitted by cmdlets called from methods are silently ignored; e.g.,
      class Foo { [string] Bar(){ Get-Item /NoSuch; return '' } }; [Foo]::new().Bar()

  • Streams 3 - 6 can be targeted as in non-class code (Write-Warning, ...) and are _passed through_.

Even with the immediate inconsistency fixed, this bifurcation of stream behavior is counter-intuitive (except for the return vs. Write-Output/implicit output distinction) and requires documenting - see https://github.com/MicrosoftDocs/PowerShell-Docs/issues/4497

The necessary change here seems relatively simple. Should we mark it for hacktoberfest? 馃檪

Should we mark it for hacktoberfest? 馃檪

In the case if nobody fixes the issue until October, 31 your duty will take it upon yourself 馃樃

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SteveL-MSFT picture SteveL-MSFT  路  3Comments

aragula12 picture aragula12  路  3Comments

manofspirit picture manofspirit  路  3Comments

andschwa picture andschwa  路  3Comments

garegin16 picture garegin16  路  3Comments