Why does Write-Output not work inside a PowerShell class method?
Asked Answered
R

4

15

I am trying to output variables using Write-Output, but it did not work inside a PowerShell class method. Write-Host is working. See the sample code below.

class sample {
  [string] sampleMethod() {
    $output = "Output message"
    try {
      Write-Output $output
      throw "error"
    }
    catch {
      Write-Output $output
      $_
    }
    return "err"
  }
}    

$obj = [sample]::new()
$obj.sampleMethod()

Is there any specific reason why Write-Output doesn't work inside a class method?

Rusert answered 11/10, 2018 at 10:19 Comment(0)
P
17

From the docs:

In class methods, no objects get sent to the pipeline except those mentioned in the return statement. There's no accidental output to the pipeline from the code.

This is fundamentally different from how PowerShell functions handle output, where everything goes to the pipeline.

If you need output just for debugging or whatever, you can use Write-Host, Write-Warning etc., which basically just write to the console.

Plurality answered 11/10, 2018 at 10:36 Comment(2)
Perhaps, someone in Microsoft once admits that abusing the stdout in function return values was a terrible terrible mistake. I certainly want to be able to write to stdout inside of a function. And I don't want that to interfere with function's return value.Polydipsia
@MartinDobšík I strongly disagree. If you want that behavior, feel free to stick to regular PowerShell functions. But I believe it was a good decision to make functions on classes behave more like you would expect.Plurality
S
10

To add to marsze's excellent answer:

Think of the method signature ([string] sampleMethod()) as a contract - you promise the user that if they call the method with 0 parameters, it'll always return exactly one [string] object.

Allowing an arbitrary number of Write-Output statements during method execution would violate that contract!

Surfboarding answered 11/10, 2018 at 11:26 Comment(1)
A good way of seeing it. Explicitly specifying the return type alone is something that makes them very different from regular PowerShell functions.Plurality
P
4

While write-output does not work inside a class' method, it does work if the method returns a script-block that is then executed outside, like so:

#Cmdlet you can't edit that outputs whilst running
function foo {
    write-output "Beginning complex operation!";
    start-sleep 2;
    write-output "Important information you would rather not have to wait for!";
    start-sleep 2;
    write-output "Operation finished!";
}

class IsClass{
    static [ScriptBlock]bar(){
        #create a ScriptBlock that the must be executed outside
        return { foo };
    }
}

& $([IsClass]::bar());
<#Output:
Beginning complex operation!
[two second wait]
Important information you would rather not have to wait for!
[two second wait]
Operation finished!
#>

This is a relatively hacky solution. As far as I am aware, though, it is the only way of writing output of cmdlets called inside the static method when the cmdlet is still running. Usage of write-host inside the cmdlet that the method calls is not an option if you do not have access to the cmdlets you are calling inside the class.

Example without using script blocks:

#Cmdlet you can't edit that outputs whilst running
function foo {
    write-output "Beginning complex operation!";
    start-sleep 2;
    write-output "Important information you would rather not have to wait for!";
    start-sleep 2;
    write-output "Operation finished!";
}

#Class that uses the mentioned cmdlet
class IsClass{
    static [void]bar(){
        #Directly invoke the method
        write-host $(foo);
    }
}

[IsClass]::bar();
<#Output:
[Awkward 4 second pause]
Beginning complex operation! Important information you would rather not have to wait for! Operation finished!

It's also worth noting that the second method results in all output showing up on one line.

A scenario in which you may wish to actually use this is if you were writing a script that would install tools using the command line. The installation uses cmdlets that you have no control over, and that take several minutes to complete (such as installing software using chocolatey). This means that if the cmdlet's progress changes (such as moving onto installing the software's dependencies) it cannot write the change to the console until the full install has completed, leaving the user in the dark as to what is currently happening.

UPDATE: as of writing this I have also come across many issues concerning the usage of scope inside script-blocks, as they do not share the scope of the context in which they were created, only the scope in which they are executed. This quite heavily invalidates a lot of what I mentioned here, as it means you can't reference the properties of the class.

UPDATE 2: UNLESS you make use of the GetNewClosure!

    static [ScriptBlock]bar(){
        #create a ScriptBlock that the must be executed outside
        $that = $this;
        return { $that.ClassVariable }.GetNewClosure();
    }
Par answered 19/11, 2019 at 13:30 Comment(0)
D
3

In addition to the other answers, you can also pipe the output of a command, for example 'Command', to Out-Host, inside a class method. This uses the same stream as Write-Host to make the output show up on the terminal.

   Command | Out-Host
Dispatcher answered 23/6, 2023 at 1:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.