Powershell: Writing to the error stream produces no output in methods of custom classes

Created on 23 May 2019  路  14Comments  路  Source: PowerShell/PowerShell

Note: Only [void]-typed methods do _not_ exhibit the problem demonstrated below.

Steps to reproduce

class Foo { [string] Bar() { Get-Item /NoSuch; return 'hi' } }; [Foo]::new().Bar()

Expected behavior

Get-Item : Cannot find path '/NoSuch' because it does not exist.
...
hi

Actual behavior

hi

That is, the error stream output was quietly suppressed.

However, the error _is_ recorded in the automatic $Error variable.

_Update_: By contrast, all other streams (warning, verbose, debug, information) are passed through.

Environment data

PowerShell Core 6.2.1
Windows PowerShell v5.1
Issue-Question Resolution-Answered

Most helpful comment

@mklement0

design intent with respect to passing the PowerShell output streams through

The intent was that streams are not a part of classes semantically speaking. Methods return values and throw errors.

All 14 comments

Write-Error doesn't work inside method, only throw works (this is maybe by design)
class Foo { [string] Bar() { Write-Error "MyError" ; return 'hi' } }; [Foo]::new().Bar()

class Foo { [string] Bar() { throw "MyError" ; return 'hi' } }; [Foo]::new().Bar()

Set ErrorAction to Stop, throws the error ( and you can use try/catch to handle the error)
class Foo { [string] Bar() { Get-Item /NoSuch -ErrorAction Stop ; return 'hi' } }; [Foo]::new().Bar()

class Foo { [string] Bar() { try { Get-Item /NoSuch -ErrorAction Stop } catch { throw "MyError:$_" } ; return 'hi' } }; [Foo]::new().Bar()

Indeed, the use of Write-Error inside a class method... the expected outcome isn't clear at all. Classes don't have stream implementations of their own, only input and output for data (and exceptions for errors).

@fMichaleczek: Yes, _terminating_ errors work, because they abort execution of the method, but my concern was about _nonterminating_ errors that you may simply want to _pass through_ - while continuing to execute the method.

@vexx32, I naively thought that given that you call cmdlets inside methods, their nonterminating errors would surface too (e.g., Get-ChildItem /nosuchdir).
Currently, there is no way to to directly surface them from methods.

I'd be more inclined to call it a bug that they leak through when the return type is void tbh. Otherwise the only way you'd be able to suppress them would be redirection or maybe $ErrorActionPreference.

Currently, there is no way to to directly surface them from methods.

I think if you really want a class to emit error records or any other stream, you should pass a PSCmdlet object as an argument or save it to a property.

@SeeminglyScience

So you think custom classes are their own world that deals in _return values only_ that then _map onto the success output stream_ - with no other streams available?

Certainly would require documentation, so that no one expects it to work differently, they way I did (happy to create an issue once we have a shared understanding).

However, the worlds are _not_ separate; _the error stream is the only exception_:

  • All other streams are passed through (which I think makes sense).
  • Additionally, $Error _does_ reflect non-terminating errors that occur in methods.
PS> class Foo { [string] Bar() { Get-Item /NoSuch; write-warning warning; write-verbose verbose -vb; write-debug -debug debug; write-information -InformationAction continue information;  return 'hi' } }; $Error.Clear(); [Foo]::new().Bar()
WARNING: warning
VERBOSE: verbose
DEBUG: debug
information
hi

So you think custom classes are their own world that deals in return values only that then map onto the success output stream - with no other streams available?

In my opinion, yeah absolutely.

However, the worlds are not separate; the error stream is the only exception:

Well, yes and no. Something that I've never liked about those cmdlets is they work in the context of the ICommandRuntime of their own command processor, not of the caller. Class methods don't have a command processor or ICommandRuntime, their invocation is generated sort of like ScriptBlock.Invoke as opposed to an advanced function processed by the Compiler. That's why those cmdlets still work and why it'd be difficult to "fix".

Just so I'm clear though, I'm not pointing at any of that as proof that it should be one way or another, it's just implementation detail. I don't have any insight on what the PowerShell team intended, this is just my opinion.

That's helpful background information, @SeeminglyScience, thanks.

@SteveL-MSFT, can you shed light on the design intent and suggest a resolution?
Having consistent behavior one way or the other would be helpful.

@mklement0 The design intent was that PowerShell classes should have semantics equivalent to .NET classes (since they are, in fact, .NET classes.) This means explicit return types, a requirement to use the return statement, variables must be initialized before being used, detected at compile-time, not run time, etc. The goal with classes was to make it possible to write more reliable (and larger) scripts in PowerShell by providing more conventional programming language semantics.

Thanks, @bpayette, but what I want to know more specifically is the design intent with respect to _passing the PowerShell output streams through_ (except the success output stream) when commands are called from inside custom-class methods.

@mklement0

design intent with respect to passing the PowerShell output streams through

The intent was that streams are not a part of classes semantically speaking. Methods return values and throw errors.

A somewhat tangential question, @bpayette, then: what of the other streams? It seems the information stream is handled well enough, and though I'll have to test this evening I am fairly sure I recall verbose and debug streams also behaving as they would outside a class context.

Indeed, @vexx32: as the code in the comment above demonstrates, streams 3 - 6 are passed through.

@vexx32 Those streams are neither _output_ nor _error_ hence they are not involved in a discussion of _output/error_ semantics.

@bpayette

Those streams are neither output nor error hence they are not involved in a discussion of _output/error_ semantics.

How unfortunate, then, that they are involved in the _implementation_ - which led to their involvement in this _discussion_.

I guess documenting this half-blending/separation of the worlds is our best option at this point: https://github.com/MicrosoftDocs/PowerShell-Docs/issues/4497

Was this page helpful?
0 / 5 - 0 ratings