Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
232 views
in Technique[技术] by (71.8m points)

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:pathologger.ps1'
    
  • putting it into a module and importing that module:

    Import-Module Logger
    

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...