Using this script: https://github.com/byterogues/powershell-bittrex-api
which I call from another script.
e.g.
$order = .\bittrex-api.ps1 -action buylimit -market BTC-TX -quantity 1 -rate 0.00011300
bittrex-api.ps1 catches an error and shows this on screen
BITTREX ERROR: API Query returned an error.
Error Message: MIN_TRADE_REQUIREMENT_NOT_MET
How can I capture the output from bittrex-api.ps1 into a variable so I can use this variable in my base script?
You can't. The script uses Write-Host to output the error. Write-Host only writes text to the console, it doesn't return any objects which means there's nothing to capture.
I would recommend modifying the script to use other cmdlets like Write-Error, Write-Output or any other Write-* which outputs to a stream (which you can redirect to the stdout-stream and save).
See http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/
To complement Frode F.'s helpful answer, which sensibly recommends modifying the script to use Write-Error for error reporting:
If modifying the code is not an option and you're using PSv5+, you can capture Write-Host output, because since PSv5 Write-Host writes to the newly introduced information output stream (to which primarily Write-Information is designed to write):
PowerShell's output streams are numbered, and the information stream has number 6 so that output-redirection expression 6>&1 redirects the information stream into the success output stream, whose number is 1, allowing regular capturing in a variable, as the following example shows:
# PSv5+
$captured = Write-Host 'write-host output' 6>&1
$captured # output what was captured -> 'write-host output'
To learn more about PowerShell's output streams and redirection, run Get-Help about_Redirection
Note:
Write-Host output captured via 6>&1 consists of one or more System.Management.Automation.InformationRecord instances, which print as if they were strings, namely by their .MessageData.Message property value, which is the string content of the argument(s) passed to Write-Host.
Therefore, any coloring that stems from the use of the -ForegroundColor and -BackgroundColor parameters is not (directly) passed through:
However, the information is preserved, namely in the .MessageData.ForegroundColor and .MessageData.BackgroundColor properties, along with the information about whether -NoNewLine was passed to Write-Host, in Boolean property .MessageData.NoNewLine
This answer shows how to recreate the original coloring from the captured objects.
By contrast, coloring via ANSI / VT escape sequences embedded in the original string argument(s) is preserved.
Note: To capture the output and also pass it through (to the success output stream), you have two options:
Simply enclose the statement in (...), but note that output will only show after all output has been collected.
($captured = Write-Host 'write-host output' 6>&1)
For streaming pass-through, use Tee-Object with the -Variable parameter:
Write-Host 'write-host output' 6>&1 | Tee-Object -Variable captured
The above techniques generally work for capturing and passing success-stream output through, irrespective of whether redirections are involved.
Related
I have a small form I'm working on but I have something I'm confused about. I have a closing event
$Form.Add_Closing({}) In there I'm wanting to stop a custom logging module but it doesn't reflect the output to the console, same if I use write-output. If I use Write-Host though, that reflects to the console. Does the Closing event just have any real output capability?
$Form.Add_Closing({
# my logging function - doesn't work
Write-Log -Stop
# Write-Output - doesn't work
Write-Output 'Test message'
# Write-Host - does work
Write-Host 'Another Test message'
})
The problem applies to all events, not just Closing:
Inside a PowerShell script block serving as a .NET event delegate:
You can call arbitrary commands...
... but their success-stream (pipeline) output is discarded.
However, output to any of PowerShell's other output streams does surface in the caller's console, as you've experienced with Write-Host.
Therefore, if you simply want to print the called commands' success output to the caller's display, you can pipe them to Out-Host:
$Form.Add_Closing({
Write-Log -Stop | Out-Host
})
Note:
Out-Host's output - unlike Write-Host's - can fundamentally neither be captured nor suppressed.
Output from Write-Host, which since v5 writes via the information stream, can be suppressed with 6>$null, and in principle be captured via the common -InformationVariable parameter, if your script is an advanced script and it is invoked with, say, ./yourScript -InformationVariable capturedInfoStream.
However, this does not work with Write-Host calls made inside event-delegate script blocks.
If you want to collect success output emitted from event-delegate script blocks for later use in the script (which also allows you to control if the collected output is sent to the script's caller or not), create a list in the script scope, to which you can append from the event-delegate script blocks:
# Initialize a list to collect event-delegate output in.
$outputFromEventDelegates = [Collections.Generic.List[object]] #()
# ...
$Form.Add_Closing({
# Call the function of interest and add its output to the
# script-scope list.
$outputFromEventDelegates.AddRange(
#(Write-Log -Stop)
)
})
# ... after the .ShowDialog() call
# Now you can access all collected output.
Write-Verbose -Verbose "Output collected from event delegates:"
$outputFromEventDelegates
I'm learning PowerShell and a vast number of articles I read strongly discourages the use of write-host telling me it's "bad practice" and almost always, the output can be displayed in another way.
So, I'm taking the advice and try to avoid use of write-host. One suggestion I found was to use write-output instead. As far as I understand, this puts everything in a pipeline, and the output is executed at the end of the script (?).
However, I have problems outputting what I want. This example demonstrates the issue:
$properties = #{'OSBuild'="910ef01.2.8779";
'OSVersion'="CustomOS 3";
'BIOSSerial'="A5FT-XT2H-5A4B-X9NM"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-output $object
$properties = #{'Site'="SQL site";
'Server'="SQL Server";
'Database'="SQL Database"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-Output $object
This way I get a nice output of the first object displaying the OS data, but the second object containing the SQL data is never displayed. I've tried renaming the variable names, and a bunch of other different stuff, but no luck.
While troubleshooting this problem, I found similar problems with suggestions to just replace write-output with write-host. This gets me very confused. Why are some people strongly discouraging write-host, while other people encourage it?
And how exactly do I output these two objects in a fashionably manner? I do not fully understand the pipeline mechanism of write-output.
Just to clarify: the problem is only a display problem:
When outputting to the console, if the first object is table-formatted (if Format-Table is applied, which happens implicitly in your case), the display columns are locked in based on that first object's properties.
Since your second output object shares no properties with the first one, it contributes nothing to the table display and is therefore effectively invisible.
By contrast, if you programmatically process the script's output - assign it to a variable or send its output through the pipeline to another command - both objects will be there.
See Charlie Joynt's answer for a helpful example of assigning the two output objects to separate variables.
The simplest solution to the display problem is to explicitly format for display each input object individually - see below.
For a given single object inside a script, you can force formatted to-display (to-host) output with Out-Host:
$object | Out-Host # same as: Write-Output $object | Out-Host
Note, however, that this outputs directly and invariably to the console only and the object is then not part of the script's data output (the objects written to the success output stream, the stream with index 1).
In other words: if you try to assign the script's output to a variable or send its output to another command in a pipeline, that object won't be there.
See below for why Out-Host is preferable to Write-Host, and why it's better to avoid Write-Host in most situations.
To apply the technique ad hoc to a given script's output as a whole, so as to make sure you see all output objects, use:
./some-script.ps1 | % { $_ | Out-String } # % is the built-in alias of ForEach-Object
Note that here too you could use Out-Host, but the advantage of using Out-String is that it still allows you to capture the for-display representation in a file, if desired.
Here's a simple helper function (filter) that you can put in your $PROFILE:
# Once defined, you can use: ./some-script.ps1 | Format-Each
Filter Format-Each { $_ | Out-String }
PetSerAl's suggestion - ./some-script.ps1 | Format-List - works in principle too, but it switches the output from the usual table-style output to list-style output, with each property listed on its own line, which may be undesired.
Conversely, however, Format-Each, if an output object is (implicitly) table-formatted, prints a header for each object.
Why Write-Output doesn't help:
Write-Output doesn't help, because it writes to where output objects go by default anyway: the aforementioned success output stream, where data should go.
If the output stream's objets aren't redirected or captured in some form, they are sent to the host by default (typically, the console), where the automatic formatting is applied.
Also, use of Write-Output is rarely necessary, because simply not capturing or redirecting a command or expression implicitly writes to the success stream; another way of putting it:
Write-Output is implied.
Therefore, the following two statements are equivalent:
Write-Output $object # write $object to the success output stream
$object # same; *implicitly* writes $object to the success output stream
Why use of Write-Host is ill-advised, both here and often in general:
Assuming you do know the implications of using Write-Host in general - see below - you could use it for the problem at hand, but Write-Host applies simple .ToString() formatting to its input, which does not give you the nice, multi-line formatting that PowerShell applies by default.
Thus, Out-Host (and Out-String) were used above, because they do apply the same, friendly formatting.
Contrast the following two statements, which print a hash-table ([hashtable]) literal:
# (Optional) use of Write-Output: The friendly, multi-line default formatting is used.
# ... | Out-Host and ... | Out-String would print the same.
PS> Write-Output #{ foo = 1; bar = 'baz' }
Name Value
---- -----
bar baz
foo 1
# Write-Host: The hashtable's *entries* are *individually* stringified
# and the result prints straight to the console.
PS> Write-Host #{ foo = 1; bar = 'baz' }
System.Collections.DictionaryEntry System.Collections.DictionaryEntry
Write-Host did two things here, which resulted in near-useless output:
The [hashtable] instance's entries were enumerated and each entry was individually stringified.
The .ToString() stringification of hash-table entries (key-value pairs) is System.Collections.DictionaryEntry, i.e., simply the type name of the instance.
The primary reasons for avoiding Write-Host in general are:
It outputs directly to the host (console) rather than to PowerShell's success output stream.
As a beginner, you may mistakenly think that Write-Host is for writing results (data), but it isn't.
In bypassing PowerShell's system of streams, Write-Host output cannot be redirected - that is, it can neither be suppressed nor captured (in a file or variable).
That said, starting with PowerShell v5.0, you can now redirect its output via the newly introduced information stream (number 6; e.g., ./some-script.ps1 6>write-host-output.txt); however, that stream is more properly used with the new Write-Information cmdlet.
By contrast, Out-Host output still cannot be redirected.
That leaves just the following legitimate uses of Write-Host:
Creating end-user prompts and colored for-display-only representations:
Your script may have interactive prompts that solicit information from the user; using Write-Host - optionally with coloring via the -ForegroundColor and -BackgroundColor parameters - is appropriate, given that prompt strings should not become part of the script's output and users also provide their input via the host (typically via Read-Host).
Similarly, you can use Write-Host with selective coloring to explicitly create friendlier for-display-only representations.
Quick prototyping: If you want a quick-and-dirty way to write status/diagnostic information directly to the console without interfering with a script's data output.
However, it is generally better to use Write-Verbose and Write-Debug in such cases.
Generally speaking the expectation is for script/functions to return a single "type" of object, often with many instances. For example, Get-Process returns a load of processes, but they all have the same fields. As you'll have seen from the tutorials, etc. you can then pass the output of Get-Process along a pipeline and process the data with subsequent cmdlets.
In your case you are returning two different types of object (i.e. with two different sets of properties). PS outputs the first object, but not the second one (which doesn't match the first) as you discovered. If you were to add extra properties to the first object that match those used in the second one, then you'd see both objects.
Write-Host doesn't care about this sort of stuff. The push-back against using this is mainly to do with (1) it being a lazy way to give feedback about script progress, i.e. use Write-Verbose or Write-Debug instead and (2) it being imperfect when it comes to passing objects along a pipeline, etc.
Clarification on point (2), helpfully raised in the comments to this answer:
Write-Host is not just imperfect with respect to the pipeline /
redirection / output capturing, you simply cannot use it for that in
PSv4 and earlier, and in PSv5+ you have to awkwardly use 6>; also,
Write-Host stringifies with .ToString(), which often produces
unhelpful representations
If your script is really just meant to print data to the console then go ahead and Write-Host.
Alternatively, you can return multiple objects from a script or function. Using return or Write-Output, just return object objects comma-separated. For example:
Test-WriteOutput.ps1
$object1 = [PSCustomObject]#{
OSBuild = "910ef01.2.8779"
OSVersion = "CustomOS 3"
BIOSSerial = "A5FT-XT2H-5A4B-X9NM"
}
$object2 = [PSCustomObject]#{
Site = "SQL site"
Server= "SQL Server"
Database="SQL Database"
}
Write-Output $object1,$object2
The run the script, assigning the output into two variables:
$a,$b = .\Test-WriteOutput.ps1
You'll see that $a is $object1 and $b is $object2.
use write-host, write-output is for pipeline (and by default on console after clear)
this has been beaten to death but can't find an exact solution for my problem.
I have a PowerShell script that can be run from the command line or from a scheduled task. I'm using the following line
Write-Output "Updating user $account" | Tee-Object $logfile -Append
to write relevant information to the screen and a log file. I need both because when run from a command line, I can physically see what's going on but when run from a scheduled task, I have no visibility to its output hence the log file.
Thing is, I'm modifying my code to use functions but as you might already know, Write-Output messes up the return values of functions when used within said functions.
What could I do that would do something similar to what I stated above without affecting the function's return value?
Thanks.
Just write to a log file. When running from the console, open another console and tail the log file.
Get-Content 'C:\path\to\the\logfile.txt' -Tail 10 -Wait
Assuming PowerShell version 5 or higher, where Write-Host writes to the information output stream (stream number 6), which doesn't interfere with the success output stream (stream number 1) and therefore doesn't pollute your function's data output:
The following is not a single command, but you could easily wrap this in a function:
Write-Host "Updating user $account" -iv msg; $msg >> $logfile
The above uses the common -InformationVariable (-iv) parameter to capture Write-Host's output in variable $msg (note how its name must be passed to -iv, i.e. without the leading $).
The message captured in $msg is then appended to file $logfile with >>, the appending redirection operator.
Note: >> is in effect an alias for Out-File -Append, and uses a fixed character encoding, both on creation and appending.
Use Add-Content and its -Encoding parameter instead, if you want to control the encoding.
Instead of explicitly writing each log line to a file, you may want to use a different approach that references the log file only at one location in the code.
Advantages:
Easy to change log path and customize the log output (e. g. prepending a timestamp), without having to modify all code locations that log something.
Captures any kind of messages, e. g. also error, verbose and debug messages (if enabled).
Captures messages of 3rd party code aswell, without having to tell them the name of the log file.
Function SomeFunction {
Write-Host "Hello from SomeFunction" # a log message
"SomeFunctionOutput" # Implicit output (return value) of the function.
# This is short for Write-Output "SomeFunctionOutput".
}
Function Main {
Write-Host "Hello from Main" # a log message
# Call SomeFunction and store its result (aka output) in $x
$x = SomeFunction
# To demonstrate that "normal" function output is not affected by log messages
$x -eq "SomeFunctionOutput"
}
# Call Main and redirect all of its output streams, including those of any
# called functions.
Main *>&1 | Tee-Object -FilePath $PSScriptRoot\Log.txt -Append
Output:
Hello from Main
Hello from SomeFunction
True
In this sample all code is wrapped in function Main. This allows us to easily redirect all output streams using the *>&1 syntax, which employs the redirection operator to "merge" the streams. This means that all commands further down the pipeline (in this example Tee-Object) receive any script messages that would normally end up in the console (except when written directly to the console, which circumvents PowerShells streams).
Possible further improvements
You may want to use try/catch in function Main, so you also capture script-terminating errors:
try {
SomeFunction # May also cause a script-terminating error, which will be catched.
# Example code that causes a script-terminating error
Write-Error "Fatal error" -ErrorAction Stop
}
catch {
# Make sure script-terminating errors are logged
Write-Error -ErrorRecord $_ -ErrorAction Continue
}
I was pointed at a question that suggests using Write-Output over Write-Host if I want commands to operate sequentially (as Write-Host doesn't put the output on the pipeline while other commands do, which means that Write-Host output can happen before or after other commands that are on the pipeline leading to very messy output): command execution ordering inside a PowerShell scriptblock
Following this advice, I made a simple function using Write-Output to mimic Write-Host's colour syntax. For ordering, this works well, so that output from commands is now sequential, but the colour output is now awful with Write-Output so that if I use any BackgroundColor at all, the results are sprayed over the screen in very ugly ways. Write-Host was tight and reliable with colour output and didn't bleed into other parts of the console so using Write-Output with colour makes for some really ugly/clunky console output.
Do I need to reset $host.ui in some way before leaving the function, or can anyone suggest a way to modify this function so that the colours remain tight to the areas that they are required for and don't bleed to other console areas?
function Write-Color ($text, $ForegroundColor, $BackgroundColor) {
$defaultFore = $host.ui.RawUI.ForegroundColor
$defaultBack = $host.ui.RawUI.BackgroundColor
if ($ForegroundColor -ne $null) { $host.ui.RawUI.ForegroundColor = $ForegroundColor }
if ($BackgroundColor -ne $null) { $host.ui.RawUI.BackgroundColor = $BackgroundColor }
Write-Output $text
$host.ui.RawUI.ForegroundColor = $defaultFore
$host.ui.RawUI.BackgroundColor = $defaultBack
}
e.g.
Write-Color "The dog sat on the couch" -ForegroundColor Red -BackgroundColor White
Write-Host is the right tool for producing (possibly colored) for-display output - as opposed to outputting data via PowerShell's success output stream, via cmdlet calls and expressions, (optionally via explicit Write-Output calls, but that's rarely needed).
This answer explains that if you mix Write-Host and success-stream output, in PowerShell v5+ what prints to the console can appear out of order.
This is a side effect of implicitly applied tabular formatting situationally being asynchronous, in an effort to collect some data before printing output so as to determine suitable column width. It happens only for output types that (a) don't have predefined format data, and (b) have 4 or fewer properties (because types with more properties default to list formatting).
The problematic behavior is discussed in GitHub issue #4594; while there's still hope for a solution, there has been no activity in a long time.
There is no good solution to this problem as of PowerShell 7.0:
There are two - suboptimal - workarounds:
(a) Pipe individual commands that trigger the asynchronous behavior to ... | Out-Host.
E.g., in the following command the command with the Select-Object call must be sent to Out-Host so as to appear correctly between the two Write-Host calls on the screen:
Write-Host '------- 1'
Get-Item . | Select-Object FullName | Out-Host
Write-Host '------- 2'
Downside: Using Out-Host means you lose the ability to capture or redirect the command's output, because it is sent directly to the host (display). Additionally, it is cumbersome to (a) know what commands trigger the problem and (b) to remember to apply the workaround to each.
(b) Replace Write-Host calls with sending strings with embedded VT (Virtual Terminal) escape sequences (for coloring) to the success output stream.
Note: Requires Windows PowerShell v5.1 on Windows 10 or PowerShell [Core] v6+
Downside: The (colored) strings become part of the code's data output and are therefore included when you capture / redirect output.
# Windows PowerShell 5.1: [char] 0x1b produces an ESC char.
$green = [char] 0x1b + '[32m'; $reset = [char] 0x1b + '[m'
# Print "green" in green.
"It ain't easy being ${green}green${reset}."
# PowerShell 6+: `e can be used inside "..." for ESC.
$yellow = "`e[33m"; $reset = "`e[m"
# Print "yellow" in yellow.
"They call me mellow ${yellow}yellow${reset}."
The fact that these strings contain ESC chars. could actually be used to filter out for-display strings from the data stream (assuming your actual data doesn't contain ESC chars.), along the lines of ... | Where-Object { -not ($_ -is [string] -and $_ -match '\e') }
Embedding VT escape sequences allows you to selectively color parts of your strings.
Achieving the same effect with Write-Host would require multiple calls with -NoNewline.
Third-party cmdlet (module) Write-ColoredOutput emulates Write-Host's syntax and uses the [console] type's attributes to turn coloring on and off, while sending the string to the success output stream.
This works well for writing an entire string in a given color, but you cannot piece together differently colored parts on a single line, because each string individually written to the success output stream invariably prints on its own line.
If you wanted a convenience wrapper around embedding VT sequences directly in strings, you could adapt the Write-HostColored function from this answer, by replacing the Write-Host calls that happen behind the scenes with VT sequences.
I would like to set up a variable in my code that would ultimately define if I'll see some output or not.
"hello" writes to stdout
"hello" > $null supresses output
My idea is something like this:
$debugOutputSwitch = $true
$outputVar = $null
if ($debugOutputSwitch){ $outputVar = **STDOUT** }
...
Write-Host "Something I want out anyway"
"Something I might not want on STDOUT" > $outputVar
If this general idea is a way to go, then STDOUT is what I'm looking for
If this idea is completely wrong...well...then I'm lost
What you want to read up on are output streams and redirection in Powershell. This includes information on all of the different output streams and how to control their relevance using built-in constructs. Just like there are the Write-Host and Write-Output cmdlets, there are also several others that control which stream to write to.
About the Output Streams
There are 6 streams in total. Make note of their numbers, because these stream identifiers are used to control which streams to redirect:
1 - Success Stream - This stream is used when passing information along the Powershell Pipeline. This is the "default" stream, but can also be written to with Write-Output.
2 - Error Stream - Errors should be written to this stream. Can be written to with Write-Error, accompanied by further error information.
3 - Warning Stream - Used to write warning information. Can be written to withWrite-Warning.
4 - Verbose Stream - Used to write verbose output. Does not display by default but can be made to display by either setting $VerbosePreference = "Continue", or by using the [CmdletBinding()] attribute on a function or script and passing in the -Verbose flag. Write to the verbose stream with Write-Verbose.
5 - Debug Stream - Used to write to the debug stream, and optionally trigger a breakpoint. Does not display or trigger a breakpoint by default, but can be controlled with the $DebugPreference variable, or by using the [CmdletBinding()] attribute on a script or function and using the -Debug flag. You can write to the debug stream by using theWrite-Debug cmdlet.
6 - Information Stream - Can be written to by Write-Host. This is the console host output and is not part of the pipeline.
Redirecting Streams
You can use redirection operators to redirect other streams to the success stream as well. Each stream above has a number associated with it. This is the numeric representation of each stream.
The redirection operators are as follows:
> - Redirect success stream to file (overwrite)
#> - Redirect the # stream to file (e.g. 2> somefile.txt)
>> - Redirect success stream to file (appends, you can also use a numbered stream as with the overwrite file operator)
>&1 - Redirect any stream to success stream (note that unlike the other redirection operators you can only redirect to the success stream. Using other stream identifiers will result in an error).
Also note that in place of a stream number, you can use * which will redirect all streams at the same time.
Here are some examples of redirecting output from one stream to another (if you're familiar with it, it's somewhat UNIX-y):
# Write success stream to file
Write-Output "Here is some text for a file" > .\somefile.txt
# Write error stream to file (you have to first
Write-Error "Some error occurred" 2> .\somefile.txt
# Redirect all error output to the success stream
$myErrorOutput = Write-Error "My error output" 2>&1
# Append all script output streams to a single file
Get-OutputFromAllStreams.ps1 *>> somefile.txt
Output to a File and the Pipeline Simultaneously
You can redirect the output stream to a file and the pipeline at the same time as well, using the Tee-Object cmdlet. This also works with variables, too:
$myString = "My Output" | Tee-Object -FilePath .\somefile.txt
$myString2 = "My Output 2" | Tee-Object -Variable varName
Sample function to show how to use the different Write- cmdlets
Note how the following function is decorated with the [CmdletBinding()] attribute. This is key in making the -Verbose and -Debug switches work without you having to define them yourself.
function Write-DifferentOutputs {
[CmdletBinding()]
# These all visible by default but only the output stream is passed down the pipeline
Write-Output "Output stream"
Write-Warning "Warning stream"
Write-Error "Error stream"
Write-Host "Information stream"
# These are not visible by default, but are written when the `-Verbose` or `-Debug` flags are passed
# You can also manually set the $VerbosePreference or $DebugPreference variables to control this without parameters
Write-Verbose "Verbose stream"
Write-Debug "Debug stream"
}
Call the above function with the -Verbose or -Debug switches to see how the behavior differs, and also call it with neither flag.
Redirecting output to $null if you really need to
If there is output that you never want to see or for some other reason using the Write- cmdlets to write to the Verbose or Debug streams isn't an option, you can still redirect output to $null or make use of the Out-Null cmdlet. Recall the numbered streams at the top of this answer, they will be referenced here:
Using redirection
# Don't forget that *> redirects ALL streams, and may be what you want
Write-Output 'Success Stream' > $null
Write-Error 'Error Stream' 2> $null
Write-Warning 'Warning Stream' 3> $null
Write-Verbose 'Verbose Stream' 4> $null
Write-Debug 'Debug Stream' 5> $null
Write-Host 'Information Stream (yes you can suppress/redirect me)' 6> $null
You can also redirect target streams per command: The following example (using the earlier Write-DifferentOutputs function) redirects all streams except for the Error and Success streams:
Note: You are not limited to redirecting targeted streams only to $null.
Write-DifferentOutputs 6>$null 5>$null 4>$null 3>$null
Using Out-Null
Remember, you can redirect other streams to the success stream by redirecting the output to &1.
# Remember, to pass information on the pipeline
# it MUST be on the success stream first
# Don't forget that *> redirects ALL streams, and may be what you want
Write-Output 'Success Stream' | Out-Null
Write-Error 'Error Stream' 2>&1 | Out-Null
Write-Warning 'Warning Stream' 3>&1 | Out-Null
Write-Verbose 'Verbose Stream' 4>&1 | Out-Null
Write-Debug 'Debug Stream' 5>&1 | Out-Null
Write-Host 'Information Stream (yes you can suppress/redirect me)' 6>&1 | Out-Null
When using Out-Host is appropriate ("Don't Cross the Streams")
Warning: Unlike Write-Host, Out-Host does not output to the information stream. Instead, it outputs directly to the host console. This makes redirection of anything written directly to Out-Host impossible short of using Start-Transcript or using a custom PowerShell host. Note that information written to the console host is still visible to external applications which may be watching the output of PowerShell, as ultimately evenOut-Host output makes it to STDOUT.
Calling yourself Out-Host is usually redundant. By default, PowerShell sends all unassigned output on the success stream here via way of the Out-Default cmdlet (which you should never callOut-Default directly). That said, one useful invocation of Out-Host is to synchronously output formatted object data to the console:
Note: You can redirect information from other output streams and output to Out-Host as well, but there is no reason to do so. Object data will only remain intact on the success stream, the other streams will first convert an object to its ToString() representation prior to redirection. This is also why piping the object to Out-Host in this case is preferable to Write-Host.
Get-Process msedge | Out-Host
One of the caveats of the different output streams is that there is no synchronicity between streams. Normally this is not an issue as PowerShell executes instructions line by line in order, and with the exception of Write-Output success stream this is not a problem with the other streams. However, many types will have a computed for-display attribute which is computed asynchronously from the script execution before the information is sent to Out-Default.
This can result in the displayed object data being intermingled with other output streams which are written to the console host. In some cases, this can even result in loss of information written to the console. "Crossing the streams", if you will, as it pertains to how the rendered output may look.
Consider the following example and output. This does not showcase the streams intermingling, but consider the trouble you would have parsing the output externally if Write-Host "end `n" were written in the middle of the table:
Write-Host "start `n"
Get-LocalUser
Write-Host "end `n"
And the output:
start
end
Name Enabled Description
---- ------- -----------
Administrator True
DefaultAccount False A user account managed by the system.
Disabled False Built-in account for guest access to the computer/domain
In particular this is problematic for types which define a table format that must calculate the column width before sending the formatted data to Out-Host for display. Types which pre-define the table width or do not format output as a table at all do not have this problem. Out-Default can take up to 300ms to calculate column width.
When Out-Host is called explicitly as part of a pipeline, however, the table width calculation is skipped for these objects as the object data never makes it to Out-Default. This is useful primarily to ensure that object data intended to be written to the console is done so in the correct order. The downside is that table columns may not be wide enough to accommodate all of your data on each row.
This all said, if you must process the console output of a script, it is recommended to format the data you wish to process into a string and use Write-Host instead, or use another method to get the data somewhere suitable for external processing. for-display formatting is not intended for use with external processing.
#mklement1's answer here dives further into the details about this if you are curious to learn more about this problem.
Redirecting whole command outputs to Write- cmdlets
You can easily pipe all output of a command or cmdlet to one of the Write- cmdlets. I'll use the Write-DifferentOutputs provided earlier in my example below, but this will work with any cmdlet, script, or command you run:
Write-DifferentOutputs *>&1 | Write-Verbose
What the above will do is only show the command output if $VerbosePreference = $Continue, or if you passed -Verbose as an argument to your script or function.
In Summarium
In your original question, you are attempting to reinvent a wheel that Powershell already supports fairly well. I would suggest that you learn how to make use of the different Write-Output cmdlets for each stream and especially learn how to make use of the Write-Warning, Write-Verbose, Write-Error, and Write-Debug cmdlets.
All right.
Thanks to all the brainiacs here for the motivation.
This answer may not be the best way to go about it, but it works!
Two things you need to understand to achieve this:
If you are used to using Write-Host, it won't work, you'll have to go with Write-Output.
You may have to learn to use a block of script as a function parameter.
One is self explanatory, so here's how to attain #2:
Function Test-SctiptBlockParam {
Param(
$scriptblock
)
if ($debugOutput) {
Invoke-Command $scriptblock
} else {
(Invoke-Command $scriptblock) > $null
}
}
Test-SctiptBlockParam -scriptblock { Write-Output "I want to see on the STDOUT sometimes" }
Finally, here is an example of my output and code