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

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();
    }

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!


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.