I'm writing a function in PowerShell that I want to be called via other PowerShell functions as well as be used as a standalone function.
With that objective in mind, I want to send a message down the pipeline using Write-Output to these other functions.
However, I don't want Write-Output to write to the PowerShell console. The TechNet page for Write-Output states:
Write-Output:
Sends the specified objects to the next command in the pipeline. If the command is the last command in the pipeline, the objects are displayed in the console.
-NoEnumerate:
By default, the Write-Output cmdlet always enumerates its output. The NoEnumerate parameter suppresses the default behavior, and prevents Write-Output from enumerating output. The NoEnumerate parameter has no effect on collections that were created by wrapping commands in parentheses, because the parentheses force enumeration.
For some reason, this -NoEnumerate switch will not work for me in either the PowerShell ISE or the PowerShell CLI. I always get output to my screen.
$data = "Text to suppress"
Write-Output -InputObject $data -NoEnumerate
This will always return 'Text to suppress' (no quotes).
I've seen people suggest to pipe to Out-Null like this:
$data = "Text to suppress"
Write-Output -InputObject $data -NoEnumerate | Out-Null
$_
This suppresses screen output, but when I use $_ I have nothing in my pipeline afterwards which defeats the purpose of me using Write-Output in the first place.
System is Windows 2012 with PowerShell 4.0
Any help is appreciated.
Write-Output doesn't write to the console unless it's the last command in the pipeline. In your first example, Write-Output is the only command in the pipeline, so its output is being dumped to the console. To keep that from happening, you need to send the output somewhere. For example:
Write-Output 5
will send "5" to the console, because Write-Output is the last and only command in the pipeline. However:
Write-Output 5 | Start-Sleep
no longer does that because Start-Sleep is now the next command in the pipeline, and has therefore become the recipient of Write-Output's data.
Try this:
Write your function as you have written it with Write-Output as the last command in the pipeline. This should send the output up the line to the invoker of the function. It's here that the invoker can use the output, and at the same time suppress writing to the console.
MyFunction blah, blah, blah | % {do something with each object in the output}
I haven't tried this, so I don't know if it works. But it seems plausible.
My question is not the greatest.
First of all Write-Output -NoEnumerate doesn't suppress output on Write-Output.
Secondly, Write-Output is supposed to write its output. Trying to make it stop is a silly goal.
Thirdly, piping Write-Output to Out-Null or Out-File means that the value you gave Write-Output will not continue down the pipeline which was the only reason I was using it.
Fourth, $suppress = Write-Output "String to Suppress" also doesn't pass the value down the pipeline.
So I'm answering my question by realizing if it prints out to the screen that's really not a terrible thing and moving on. Thank you for your help and suggestions.
Explicitly storing the output in a variable would be more prudent than trying to use an implicit automatic variable. As soon as another command is run, that implicit variable will lose the prior output stored in it. No automatic variable exists to do what you're asking.
If you want to type out a set of commands without storing everything in temporary variables along the way, you can write a scriptblock at the command line as well, and make use of the $_ automatic variable you've indicated you're trying to use.
You just need to start a new line using shift + enter and write the code block as you would in a normal scriptblock - in which you could use the $_ automatic variable as part of a pipeline.
Related
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 have the function below that produce multiple outputs, is there a way I can put all the outputs of the function in a text file. I tried below to use Out-File it did not work any suggestions?
cls
function functionAD {Write-output ""...}
functionAD | Out-File -FilePath C:\test\task5.txt -Append
the script above still did not work.
UPDATE: This is, in fact, possible if you overwrite the Write-Host function. It can be done like this:
function Write-Host($toWrite) {
Write-Output $toWrite
}
Copy and paste this code into your PowerShell console, then run the program.
Don't worry about permanently overwriting the Write-Host command, this will only last for the current session.
OLD COMMENT:
Unfortunately, Write-Host can not be rerouted to another file stream. It is the only 'write' command that acts in that way. That is why PowerShell programmers generally try to avoid using it unless there is a specific reason to. It is intended for messages sent directly to the user and is thus send to the program (powershell) itself rather than a console.
I would suggest using some other command if the function is your own. Write-Output is always a safe bet because it can be redirected to any other stream.
Here is a link if you have more questions: https://devblogs.microsoft.com/scripting/understanding-streams-redirection-and-write-host-in-powershell/
I'm writing a script that (by necessity) has to call a rather noisy set of other cmdlets. I'd like to the printing from the other cmdlets to not be displayed so I only see the status messages from my own script.
I've tried > $null and | Out-Null, but those only swallow returned values, not text printed via Write-Host. How can I hide/prevent text being printed "down the stack"?
Try to define your dummy function Write-Host before calling noisy cmdlets, e.g.
function Write-Host {}
If they call Write-Host literally then this should help.
If I've read your post correctly, you'd like to silence the Write-Host cmdlet. If we consider command precedence, we know that functions will be run before cmdlets, if they have the same name. Therefore, I'd recommend you create a Write-Host function that doesn't write anything. Here's an example that highlights this possibility.
I am having a little confusion about the various ways to print (echo) to the console. I have seen that there are multiple ways to write output to the console, such as:
Write-Host "Hello world1"
"Hello World2"
Out-Host -InputObject "Hello World3"
All three ways will print to the console. The middle one is somehow simpler and less verbose and easier to use. I also find that when you write a function such as:
function GetValues()
{
"1"
"2"
}
It still returns two strings in the pipeline:
And I'm still able to print out the values:
foreach ($s in GetValues)
{
Write-Host "s: " $s
}
The thing that I have found was that using just the quoted string doesn't always appear on custom hosts, and that I have had to use Write-Host to get values to print on custom hosts.
Somehow I find this confusing. Is "Print something" supposed to be an alias to Write-Host or what is the intent?
Default behaviour of PowerShell is just to dump everything that falls out of a pipeline without being picked up by another pipeline element or being assigned to a variable (or redirected) into Out-Host. What Out-Host does is obviously host-dependent.
Just letting things fall out of the pipeline is not a substitute for Write-Host which exists for the sole reason of outputting text in the host application.
If you want output, then use the Write-* cmdlets. If you want return values from a function, then just dump the objects there without any cmdlet.
The middle one writes to the pipeline. Write-Host and Out-Host writes to the console. 'echo' is an alias for Write-Output which writes to the pipeline as well. The best way to write to the console would be using the Write-Host cmdlet.
When an object is written to the pipeline it can be consumed by other commands in the chain. For example:
"hello world" | Do-Something
but this won't work since Write-Host writes to the console, not to the pipeline (Do-Something will not get the string):
Write-Host "hello world" | Do-Something
There are a number of different ways to output messages. What is the effective difference between outputting something via Write-Host, Write-Output, or [console]::WriteLine?
I also notice that if I use:
write-host "count=" + $count
The + gets included in the output. Why's that? Shouldn't the expression be evaluated to produce a single concatenated string before it gets written out?
Write-Output should be used when you want to send data on in the pipe line, but not necessarily want to display it on screen. The pipeline will eventually write it to out-default if nothing else uses it first.
Write-Host should be used when you want to do the opposite.
[console]::WriteLine is essentially what Write-Host is doing behind the scenes.
Run this demonstration code and examine the result.
function Test-Output {
Write-Output "Hello World"
}
function Test-Output2 {
Write-Host "Hello World" -foreground Green
}
function Receive-Output {
process { Write-Host $_ -foreground Yellow }
}
#Output piped to another function, not displayed in first.
Test-Output | Receive-Output
#Output not piped to 2nd function, only displayed in first.
Test-Output2 | Receive-Output
#Pipeline sends to Out-Default at the end.
Test-Output
You'll need to enclose the concatenation operation in parentheses, so that PowerShell processes the concatenation before tokenizing the parameter list for Write-Host, or use string interpolation
write-host ("count=" + $count)
# or
write-host "count=$count"
BTW - Watch this video of Jeffrey Snover explaining how the pipeline works. Back when I started learning PowerShell I found this to be the most useful explanation of how the pipeline works.
Apart from what Andy mentioned, there is another difference which could be important - write-host directly writes to the host and return nothing, meaning that you can't redirect the output, e.g., to a file.
---- script a.ps1 ----
write-host "hello"
Now run in PowerShell:
PS> .\a.ps1 > someFile.txt
hello
PS> type someFile.txt
PS>
As seen, you can't redirect them into a file. This maybe surprising for someone who are not careful.
But if switched to use write-output instead, you'll get redirection working as expected.
Here's another way to accomplish the equivalent of Write-Output. Just put your string in quotes:
"count=$count"
You can make sure this works the same as Write-Output by running this experiment:
"blah blah" > out.txt
Write-Output "blah blah" > out.txt
Write-Host "blah blah" > out.txt
The first two will output "blah blah" to out.txt, but the third one won't.
"help Write-Output" gives a hint of this behavior:
This cmdlet is typically used in scripts to display strings and other
objects on the console. However, because the default behavior is to
display the objects at the end of a pipeline, it is generally not
necessary to use the cmdlet.
In this case, the string itself "count=$count" is the object at the end of a pipeline, and is displayed.
For usages of Write-Host, PSScriptAnalyzer produces the following diagnostic:
Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.
See the documentation behind that rule for more information. Excerpts for posterity:
The use of Write-Host is greatly discouraged unless in the use of commands with the Show verb. The Show verb explicitly means "show on the screen, with no other possibilities".
Commands with the Show verb do not have this check applied.
Jeffrey Snover has a blog post Write-Host Considered Harmful in which he claims Write-Host is almost always the wrong thing to do because it interferes with automation and provides more explanation behind the diagnostic, however the above is a good summary.
From my testing Write-Output and [Console]::WriteLine() perform much better than Write-Host.
Depending on how much text you need to write out this may be important.
Below if the result of 5 tests each for Write-Host, Write-Output and [Console]::WriteLine().
In my limited experience, I've found when working with any sort of real world data I need to abandon the cmdlets and go straight for the lower level commands to get any decent performance out of my scripts.
measure-command {$count = 0; while ($count -lt 1000) { Write-Host "hello"; $count++ }}
1312ms
1651ms
1909ms
1685ms
1788ms
measure-command { $count = 0; while ($count -lt 1000) { Write-Output "hello"; $count++ }}
97ms
105ms
94ms
105ms
98ms
measure-command { $count = 0; while ($count -lt 1000) { [console]::WriteLine("hello"); $count++ }}
158ms
105ms
124ms
99ms
95ms
Regarding [Console]::WriteLine() - you should use it if you are going to use pipelines in CMD (not in powershell). Say you want your ps1 to stream a lot of data to stdout, and some other utility to consume/transform it. If you use Write-Host in the script it will be much slower.