Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 679
Samselvaprabu Avatar asked Oct 11 '18 10:10

Samselvaprabu


People also ask

What does write-output do in PowerShell?

Write-Output sends objects to the primary pipeline, also known as the "output stream" or the "success pipeline." To send error objects to the error pipeline, use Write-Error . This cmdlet is typically used in scripts to display strings and other objects on the console.

How do I print output in PowerShell?

The first method of printing output is using the Write-Output cmdlet. This cmdlet is used to pass objects in the pipeline to the successive commands. In case of the command being last, the final object is written to the console. To pass on error objects, Write-Error cmdlet is used.

What is the difference between write host and write-output in PowerShell?

In a nutshell, Write-Host writes to the console itself. Think of it as a MsgBox in VBScript. Write-Output , on the other hand, writes to the pipeline, so the next command can accept it as its input. You are not required to use Write-Output in order to write objects, as Write-Output is implicitly called for you.

How do you output a variable in PowerShell?

The echo command is used to print the variables or strings on the console. The echo command has an alias named “Write-Output” in Windows PowerShell Scripting language. In PowerShell, you can use “echo” and “Write-Output,” which will provide the same output.


2 Answers

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!

like image 129
Mathias R. Jessen Avatar answered Oct 06 '22 08:10

Mathias R. Jessen


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();
    }
like image 44
Max Hay Avatar answered Oct 06 '22 10:10

Max Hay