Write-Host vs Write-Information in PowerShell 5
It is well known that Write-Host
is evil.
In PowerShell 5
, Write-Information
is added and is considered to replace Write-Host
.
But, really, which is better?Write-Host
is evil for it does not use pipeline, so the input message can't get reused.
But, what Write-Host
do is just to show something in the console right? In what case shall we reuse the input?
Anyway, if we really want to reuse the input, why not just write something like this:
$foo = "Some message to be reused like saving to a file"
Write-Host $foo
$foo | Out-File -Path "D:\foo.log"
Another Cons of Write-Host
is that, Write-Host
can specified in what color the messages are shown in the console by using -ForegroundColor
and -BackgroundColor
.
On the other side, by using Write-Information
, the input message can be used wherever we want via the No.6 pipeline. And doesn't need to write the extra codes like I write above. But the dark side of this is that, if we want to write messages to the console and also saved to the file, we have to do this:
# Always set the $InformationPreference variable to "Continue"
$InformationPreference = "Continue";
# if we don't want something like this:
# ======= Example 1 =======
# File Foo.ps1
$InformationPreference = "Continue";
Write-Information "Some Message"
Write-Information "Another Message"
# File AlwaysRunThisBeforeEverything.ps1
.\Foo.ps1 6>"D:\foo.log"
# ======= End of Example 1 =======
# then we have to add '6>"D:\foo.log"' to every lines of Write-Information like this:
# ======= Example 2 =======
$InformationPreference = "Continue";
Write-Information "Some Message" 6>"D:\foo.log"
Write-Information "Another Message" 6>"D:\foo.log"
# ======= End of Example 2 =======
A little bit redundant I think.
I only know a little aspect of this "vs" thing, and there must have something out of my mind. So is there anything else that can make me believe that Write-Information
is better than Write-Host
, please leave your kind answers here.
Thank you.
The Write-*
cmdlets allow you to channel the output of your PowerShell code in a structured way, so you can easily distinguish messages of different severity from each other.
-
Write-Host
: display messages to an interactive user on the console. Unlike the otherWrite-*
cmdlets this one is neither suitable nor intended for automation/redirection purposes. Not evil, just different. -
Write-Output
: write the "normal" output of the code to the default (success) output stream ("STDOUT"). -
Write-Error
: write error information to a separate stream ("STDERR"). -
Write-Warning
: write messages that you consider warnings (i.e. things that aren't failures, but something that the user should have an eye on) to a separate stream. -
Write-Verbose
: write information that you consider more verbose than "normal" output to a separate stream. -
Write-Debug
: write information that you consider relevant for debugging your code to a separate stream.
Write-Information
is just a continuation of this approach. It allows you to implement log levels in your output (Debug
, Verbose
, Information
, Warning
, Error
) and still have the success output stream available for regular output.
As for why Write-Host
became a wrapper around Write-Information
: I don't know the actual reason for this decision, but I'd suspect it's because most people don't understand how Write-Host
actually works, i.e. what it can be used for and what it should not be used for.
To my knowledge there isn't a generally accepted or recommended approach to logging in PowerShell. You could for instance implement a single logging function like @JeremyMontgomery suggested in his answer:
function Write-Log {
Param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[Parameter(Mandatory=$false, Position=1)]
[ValidateSet('Error', 'Warning', 'Information', 'Verbose', 'Debug')]
[string]$LogLevel = 'Information'
)
switch ($LogLevel) {
'Error' { ... }
'Warning' { ... }
'Information' { ... }
'Verbose' { ... }
'Debug' { ... }
default { throw "Invalid log level: $_" }
}
}
Write-Log 'foo' # default log level: Information
Write-Log 'foo' 'Information' # explicit log level: Information
Write-Log 'bar' 'Debug'
or a set of logging functions (one for each log level):
function Write-LogInformation {
Param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$Message
)
...
}
function Write-LogDebug {
Param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$Message
)
...
}
...
Write-LogInformation 'foo'
Write-LogDebug 'bar'
Another option is to create a custom logger object:
$logger = New-Object -Type PSObject -Property @{
Filename = ''
Console = $true
}
$logger | Add-Member -Type ScriptMethod -Name Log -Value {
Param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[Parameter(Mandatory=$false, Position=1)]
[ValidateSet('Error', 'Warning', 'Information', 'Verbose', 'Debug')]
[string]$LogLevel = 'Information'
)
switch ($LogLevel) {
'Error' { ... }
'Warning' { ... }
'Information' { ... }
'Verbose' { ... }
'Debug' { ... }
default { throw "Invalid log level: $_" }
}
}
$logger | Add-Member -Type ScriptMethod -Name LogDebug -Value {
Param([Parameter(Mandatory=$true)][string]$Message)
$this.Log($Message, 'Debug')
}
$logger | Add-Member -Type ScriptMethod -Name LogInfo -Value {
Param([Parameter(Mandatory=$true)][string]$Message)
$this.Log($Message, 'Information')
}
...
Write-Log 'foo' # default log level: Information
$logger.Log('foo') # default log level: Information
$logger.Log('foo', 'Information') # explicit log level: Information
$logger.LogInfo('foo') # (convenience) wrapper method
$logger.LogDebug('bar')
Either way you can externalize the logging code by
-
putting it into a separate script file and dot-sourcing that file:
. 'C:\path\to\logger.ps1'
-
putting it into a module and importing that module:
Import-Module Logger
To complement Ansgar's helpful and comprehensive answer:
Write-Host
became (in essence) a wrapper forWrite-Information -InformationAction Continue
in PSv5, presumably because:
it enables suppressing or redirecting
Write-Host
messages, which was not previously possible (in PowerShell 4 or below,Write-Host
bypassed PowerShell's streams and output directly to the host),while preserving backward compatibility in that the messages are output by default - unlike with
Write-Information
, whose default behavior is to be silent (because it respects preference variable$InformationPreference
, whose default value isSilentlyContinue
).
While Write-Host
is therefore now (PSv5+) a bit of a misnomer — it doesn't necessarily write to the host anymore — it still has one distinct advantage over Write-Information
(as you state): it can produce colored output with -ForegroundColor
and -BackgroundColor
.
Ansgar's answer has the conventional logging perspective covered, but PowerShell's Start-Transcript
cmdlet may serve as a built-in alternative (see below).
As for your desire to output messages to the host while also capturing them in a log file:
PowerShell's session transcripts - via Start-Transcript
and Stop-Transcript
- may give you what you want.
As the name suggests, transcripts capture whatever prints to the screen (without coloring), which therefore by default includes success output, however.
Applied to your example:
$null = Start-Transcript "D:\foo.log"
$InformationPreference = "Continue"
Write-Information "Some Message"
Write-Information "Another Message"
$null = Stop-Transcript
The above will print messages to both the screen and the transcript file; note that, curiously, only in the file will they be prefixed with INFO:
.
(By contrast, Write-Warning
, Write-Verbose
and Write-Debug
- if configured to produce output - use prefix WARNING:
, VERBOSE:
, DEBUG:
both on-screen and in the file; similarly, Write-Error
produces "noisy" multiline input both on-screen and in the file.)
Note one bug that only affects Windows PowerShell (it has been fixed in PowerShell [Core].Thanks, JohnLBevan.): output from Write-Information
shows up in the transcript file (but not on the screen) even when $InformationPreference
is set to SilentlyContinue
(the default); the only way to exclude Write-Information
output (via the preference variable or -InformationAction
parameter) appears to be a value of Ignore
- which silences the output categorically - or, curiously, Continue
, in which it only prints to the console, as PetSerAl points out.
In a nutshell, you can use Start-Transcript
as a convenient, built-in approximation of a logging facility, whose verbosity you can control from the outside via the preference variables ($InformationPreference
, $VerbosePreference
, ...), with the following important differences from conventional logging:
Generally, what goes into the transcript file is also output to the console (which could generally be considered a plus).
-
However, success output (data output) is by default also sent to the transcript - unless you capture it or suppress it altogether - and you cannot selectively keep it out of the transcript:
If you capture or suppress it, it won't show in in the host (the console, by default) either[1].
The inverse, however, is possible: you can send output to the transcript only (without echoing it in the console), by way of
Out-Default -Transcript
Thanks, PetSerAl; e.g.,'to transcript only' | Out-Default -Transcript
; however, as of PowerShell 7.0 this appears to log the output twice in the transcript; also note thatOut-Default
is generally not meant to be called from user code - see this answer.
-
Generally, external redirections (applying
>
to a call to a script that internally performs transcription) keep streams out of the transcript, with two exceptions, as of PowerShell 7.0:-
Write-Host
output, even if6>
or*>
redirections are used. - Error output, even if
2>
or*>
redirections are used.
However, using$ErrorActionPreference = 'SilentlyContinue'
/'Ignore'
does keep non-terminating errors out of the transcript, but not terminating ones.
-
Transcript files aren't line-oriented (there's a block of header lines with invocation information, and there's no guarantee that output produced by the script is confined to a line), so you cannot expect to parse them in a line-by-line manner.
[1] PetSerAl mentions the following limited and somewhat cumbersome workaround (PSv5+) for sending success output to the console only, which notably precludes sending the output through the pipeline or capturing it:'to console only' | Out-String -Stream | ForEach-Object { $Host.UI.WriteLine($_) }
PowerShell is about automation.
Sometimes, you run a script multiple times a day and you don't want to see the output all the time.
Write-Host
has no possibility of hiding the output. It gets written on the Console, no matter what.
With Write-Information
, you can specify the -InformationAction
Parameter on the Script. With this parameter, you can specify if you want to see the messages (-InformationAction Continue
) or not (-InformationAction SilentlyContinue
)
Edit:
And please use "Some Message" | out-file D:\foo.log
for logging, and neither Write-Host
or Write-Information