0

I teach a scripting class for my local university. One of my students is stuck and asked me to look at her code and give her some clues. I added a few Write-Output statements to get an idea what was going on and the output of those Write-Output statements ended up in an open output file and not on the console. I am running PowerShell 7.2.2 on Ubuntu 20.04 on Multipass on my Macbook Pro. I stripped down the script to the following (this script is in tcowan/powershellbug) on Github:

#!/usr/bin/env pwsh

if ($args.length -lt 2) {
    write-Output("Usage: ./f.ps1 path/to/output/file recordcount")
    exit 1
}

# Convert arg[1] to integer and set as recordCount
try{
    [int]$recordCount = $args[1]    # The number of records that should be added to the output file
}
catch {
    Write-Output("Error: Unable to convert arg[2] to integer")
    exit 1
}

if ($recordCount -lt 0 -or -not ($recordCount -is [Int32]))
{
    Write-Error "Error: RecordCount must be a number and cannot be less than 0"
    exit 1
}

# Create output file
try{
    # Set outputFile path
    [string]$outputFile = $args[0]
    New-Item -path $outputFile -force -erroraction stop | Out-Null
} 
catch {
    Write-Error -Message "Error: Unable to create new output file"
    exit 1
}

Write-Output ("Writing $($recordCount) records to the output file")

function WriteToFile ($outputString)
{
    try {
        add-content -path $outputFile -value $outputString 
    }
    catch {
        Write-Output "Write failed to file $($outputFile): $($_)"
        exit 1
    }
}

function SetOutputTed ($newCmd)
{

    Write-Output("this string should appear on the console and not in my output file")
    WriteTofile($newCmd)

}

for($i = 0; $i -lt $recordCount; $i++)
{
    write-output("This string should appear on the console and does.")
    [string]$tempString = SetOutputTed ("Record $($i)")
    WriteToFile($tempString)
    write-output("This string should also appear on the console and does.")
}

exit 0

I reread the documentation of Write-Output, and found this:

This cmdlet is typically used in scripts to display strings and other objects on the console.

I committed the above script to a public repo on Github, and opened an issue. I must not understand PowerShell because this behavior was surprising. I got the following response to my issue number 17074:

Write-Output does not write directly to the console but instead writes to the output stream. If not captured (e.g. in a variable assignment) it will eventually make it's way to the console.

In this case you are assigning the output to the variable $tempString so this is the expected behavior.

I ran my test script on my Windows 10 system and got the same result. I don't recall which version of PowerShell is on my Windows machine but I think it is 7.x.

Changing the Write-Output calls to Write-Host is one workaround which works. What I am seeking is understanding.

Since I had to scan through 15 screens of open Github issues containing the string "Write-Output", I assume the developers are busy. I thought I would see if the experts here at StackOverflow could help me understand the response I received to this issue. I would like to share this with my students.

  • 1
    Well, the answer you got is correct, I'm not sure what more you're looking for. `Write-Output` writes to the output stream. It's a feature of PowerShell that you can store output in a variable, which is very useful when invoking an external command (for example, `$bashpid = pidof bash`). This works by capturing standard output, which is what you should think of when seeing `Write-Output`. By the same token, `$s = [my function that uses Write-Output]` will put the output of the function in `$s` and not the console. Since you then write `$s` to a file, of course it ends up in the file as well. – Jeroen Mostert Mar 28 '22 at 18:51
  • 2
    "standard output" (sometimes called "stdout") is a redirectable stream. When you use the `>` operation (e.g., in CMD, `DIR > file.txt`), you are telling the system to take what the DIR command sends to the standard output, and put it in file.txt instead. `Write-Output` in PowerShell sends the output to this redirectable stream. If you want to output something that is NOT redirectable - that is, you want it to appear on the console no matter what - `Write-Host` does this, by bypassing the standard streams. – Jeff Zeitlin Mar 28 '22 at 18:56
  • I hope the linked duplicate explains it; let us know if it doesn't. – mklement0 Mar 28 '22 at 19:07
  • Btw, the [`Write-Output` docs](https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/write-output) stating "This cmdlet is typically used in scripts to display strings and other objects on the console." is quite unfortunate; the purpose of `Write-Output` is to output _data_, analogous to _return values_ in other languages. Only if that data isn't captured or redirected does it end up printing to the console. I encourage you to open at issue at https://github.com/MicrosoftDocs/PowerShell-Docs/issues/new/choose – mklement0 Mar 28 '22 at 19:11
  • @JeffZeitlin `Write-Host` writes to the [information stream](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/write-information) (#6), so it actually is redirectable. This is possible since PowerShell 5.0. `Write-Host 'foo' 6>out.txt` – zett42 Mar 28 '22 at 19:12
  • In functions, its better to replace the `write-output` with `return` , same goes for the `exit` codes in functions. When using try/catch, the `catch` will only be triggered by a terminating error, so if you do not specify `-ErrorAction stop` or the cmdlet somehow terminate , the catch will never be triggered. – Vasil Nikolov Mar 28 '22 at 19:12
  • @zett42 - Noted; even though I routinely use 5.1 (with occasional forays into 7.2), I still seem to fundamentally think in 3.0 mode. :) – Jeff Zeitlin Mar 28 '22 at 19:16

0 Answers0