powershellpowershell-5.0

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.


Solution

  • 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-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