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 other Write-*
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