PowerShell 5中的写主机与写信息

时间:2016-07-22 09:51:36

标签: powershell powershell-v5.0

众所周知,Write-Host是邪恶的。 在PowerShell 5中,Write-Information已添加,并被视为替换Write-Host

但是,真的,哪个更好? Write-Host因为它不使用管道而是邪恶的,因此输入消息无法重复使用 但是,Write-Host做的只是在控制台中显示一些内容吗?在什么情况下我们应该重复使用输入?
无论如何,如果我们真的想重用输入,为什么不写这样的东西:

$foo = "Some message to be reused like saving to a file"
Write-Host $foo
$foo | Out-File -Path "D:\foo.log"

Write-Host的另一个缺点是Write-Host可以使用-ForegroundColor-BackgroundColor指定消息在控制台中显示的颜色。

另一方面,通过使用Write-Information,可以通过No.6管道在任何我们想要的地方使用输入消息。并且不需要像我上面写的那样编写额外的代码。但是黑暗的一面是,如果我们想要将消息写入控制台并保存到文件中,我们必须这样做:

# 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 =======

我认为有点多余。

我只知道这个“vs”事物的一个方面,我必须要有一些东西。那么还有什么能让我相信Write-InformationWrite-Host好,请在这里留下你的答案。
谢谢。

5 个答案:

答案 0 :(得分:30)

Write-* cmdlet允许您以结构化方式引导PowerShell代码的输出,因此您可以轻松区分不同严重程度的消息。

  • Write-Host:在控制台上向交互式用户显示消息。与其他Write-* cmdlet不同,这个cmdlet既不适合也不打算用于自动化/重定向目的。不是邪恶,只是不同。
  • Write-Output:写下"正常"将代码输出到默认(成功)输出流(" STDOUT")。
  • Write-Error:将错误信息写入单独的流(" STDERR")。
  • Write-Warning:将您认为警告的消息(即不是故障的消息,但用户应该关注的内容)写入单独的流中。
  • Write-Verbose:写下您认为比#34;正常"更详细的信息输出到单独的流。
  • Write-Debug:将您认为与调试代码相关的信息写入单独的流中。

Write-Information只是这种方法的延续。它允许您在输出中实现日志级别(DebugVerboseInformationWarningError)并且仍然可以使用成功输出流定期输出。

至于为什么Write-Host成为Write-Information的封套人:我不知道这个决定的实际原因,但我怀疑它是因为大多数人不明白Write-Host实际上是如何运作的,即它可以用于什么以及它不应该用于什么。

据我所知,目前还没有一种普遍接受或推荐的PowerShell登录方法。例如,您可以在其答案中实现一个像@JeremyMontgomery这样的日志记录功能:

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'

或一组日志记录功能(每个日志级别一个):

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'

另一种选择是创建自定义记录器对象:

$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')

无论哪种方式,您都可以通过

外化日志记录代码
  • 将其放入单独的脚本文件和dot-sourcing该文件:

    . 'C:\path\to\logger.ps1'
    
  • 将其放入module并导入该模块:

    Import-Module Logger
    

答案 1 :(得分:16)

补充Ansgar's helpful and comprehensive answer

Write-Host(实质上)成为了一个包装纸 PSv5中的Write-Information -InformationAction Continue ,大​​概是因为:

  • 启用抑制或重定向Write-Host消息,这在以前是不可能的(在PowerShell 4或更低版本中,Write-Host绕过PowerShell的流并直接输出到主机),

  • 虽然保留向后兼容性,但默认情况下输出消息 - 与Write-Information不同,其默认行为是无声(因为它尊重偏好变量$InformationPreference,其默认值为SilentlyContinue)。

虽然Write-Host现在(PSv5 +)有点用词不当 - 它不一定写入主机 - 它仍然具有一个明显的优势而不是{ {1}} (如您所述):它可以使用Write-Information-ForegroundColor生成彩色输出

Ansgar的回答涵盖了传统日志记录视角,但PowerShell的-BackgroundColor cmdlet 可能充当内置替代方案(见下文)。

至于你希望向主机输出消息,同时也在日志文件中捕获它们

PowerShell的会话记录 - 通过 Start-TranscriptStart-Transcript - 可以为您提供所需内容。

顾名思义,成绩单可以捕获任何打印到屏幕上(没有着色),因此默认包含成功输出,但是。
适用于您的示例:

Stop-Transcript

以上将打印消息 屏幕转录文件;请注意,奇怪的是,只有文件中的才会以$null = Start-Transcript "D:\foo.log" $InformationPreference = "Continue" Write-Information "Some Message" Write-Information "Another Message" $null = Stop-Transcript 作为前缀。
(相比之下,INFO:Write-WarningWrite-Verbose - 如果配置为生成输出 - 请使用前缀Write-DebugWARNING:VERBOSE:屏幕和文件;类似地,DEBUG:在屏幕上和文件中产生“嘈杂”的多行输入。)

注意一个奇怪(我不清楚这是一个错误还是设计,在PSv5.1中观察到):来自Write-Error的输出显示在转录文件中(但不在屏幕上),即使Write-Information设置为$InformationPreference (默认值);排除SilentlyContinue输出(通过首选项变量或Write-Information参数)的唯一方法似乎是-InformationAction的值 - 它会明确地使输出静音 - 或者,奇怪的是{{1} },它只会打印到控制台,就像PetSerAl指出的那样。

简而言之,您可以使用Ignore作为日志记录工具的方便的内置近似,您可以通过偏好变量从外部控制其详细程度(ContinueStart-Transcript,...),与传统日志记录具有以下重要差异

  • 一般来说,转录文件中的内容是输出到控制台(通常可以认为是加号)。

  • 但是,成功输出(数据输出)默认发送到成绩单 - 除非您捕获它或< em>完全压制 - 你不能选择性地将它从成绩单中删除:

    • 如果您捕获或禁止它,它将不会在主机(控制台,默认情况下)中显示 [1]

    • 然而,反过来是可能的:您可以通过 $InformationPreference 将输出发送到成绩单(不在控制台中回显) ,PetSerAl ;例如,
      $VerbosePreference

  • 从Windows PowerShell v5.1开始, 外部重定向通常会将流保留在转录本之外,有两个例外 PowerShell Core v6.0.0-beta.5:

    • Out-Default -Transcript输出,即使使用'to transcript only' | Out-Default -TranscriptWrite-Host重定向也是如此。
    • 错误输出,即使使用6>*>重定向也是如此 但是,使用2> / *> 非终止错误保留在记录中,但不会终止
  • 脚本文件不是面向行的(有一个带有调用信息的标题行块,并且无法保证脚本生成的输出仅限于一行),所以你不能期望在它们中解析它们逐行的方式。

[1] PetSerAl提到了以下有限且有些麻烦的解决方法(PSv5 +),仅用于将成功输出发送到控制台,这显着排除了通过管道发送输出或捕获它:
$ErrorActionPreference = 'SilentlyContinue'

答案 2 :(得分:4)

PowerShell是关于自动化的。

有时,您每天都会多次运行脚本,并且您不希望一直看到输出。

Write-Host无法隐藏输出。无论如何,它都会写在控制台上。

使用Write-Information,您可以在脚本上指定-InformationAction参数。使用此参数,您可以指定是否要查看消息(-InformationAction Continue)或不(-InformationAction SilentlyContinue

编辑: 请使用"Some Message" | out-file D:\foo.log进行日志记录,Write-HostWrite-Information

答案 3 :(得分:0)

这是我最近用于我的脚本的更专业的日志记录功能的通用版本。

这种情况是,当我需要做一些事情作为计划任务时,我通常会创建一个通用脚本,或者在一个模块中执行“繁重的工作”,然后是一个处理特定细节的调用脚本。工作,比如从XML配置,日志记录,通知等获取参数

内部脚本使用 Write-Error Write-Warning Write-Verbose ,调用脚本将所有输出流重定向到此函数的管道,它捕获带有时间戳,级别和消息的csv文件中的消息记录。

在这种情况下,它的目标是PoSh v.4,所以我基本上使用Write-Verbose作为Write-Information的替身,但同样的想法。如果我在Some-Script.ps1(参见示例)中使用了Write-Host而不是Write-Verbose或Write-Information,则Add-LogEntry函数将不会捕获并记录消息。如果要使用它来适当地捕获更多流,请在switch语句中添加条目以满足您的需求。

在这种情况下, -PassThru 开关基本上是一种方法,可以准确地解决您提到的关于写入日志文件以及输出到控制台(或另一个变量,或者管道)。在这个实现中,我为对象添加了“Level”属性,但希望你能看到这一点。我的用例是将日志条目传递给变量,以便检查错误,并在发生错误时在SMTP通知中使用。

function Add-LogEntry {
[CmdletBinding()]
param (
    # Path to logfile
    [Parameter(ParameterSetName = 'InformationObject', Mandatory = $true, Position = 0)]
    [Parameter(ParameterSetName = 'Normal', Mandatory = $true, Position = 0)]
    [String]$Path,

    # Can set a message manually if not capturing an alternate output stream via the InformationObject parameter set.
    [Parameter(ParameterSetName = 'Normal', Mandatory = $true)]
    [String]$Message,

    # Captures objects redirected to the output channel from Verbose, Warning, and Error channels
    [ValidateScript({ @("VerboseRecord", "WarningRecord", "ErrorRecord") -Contains $_.GetType().name })]
    [Parameter(ParameterSetName = 'InformationObject', Mandatory = $true, ValueFromPipeline = $true)]
    $InformationObject,

    # If using the message parameter, must specify a level, InformationObject derives level from the object.
    [ValidateSet("Information", "Warning", "Error")]
    [Parameter(ParameterSetName = 'Normal', Mandatory = $true, Position = 2)]
    [String]$Level,

    # Forward the InformationObject down the pipeline with additional level property.
    [Parameter(ParameterSetName = 'InformationObject', Mandatory = $false)]
    [Switch]$PassThru
)
Process {
    # If using an information object, set log entry level according to object type.
    if ($PSCmdlet.ParameterSetName -eq "InformationObject") {
        $Message = $InformationObject.ToString()

        # Depending on the object type, set the error level, 
        # add entry to cover "Write-Information" output here if needed
        switch -exact ($InformationObject.GetType().name) {
            "VerboseRecord" { $Level = "Information" }
            "WarningRecord" { $Level = "Warning" }
            "ErrorRecord" { $Level = "Error" }
        }
    }

    # Generate timestamp for log entry
    $Timestamp = (get-date).Tostring("yyyy\-MM\-dd\_HH\:mm\:ss.ff")
    $LogEntryProps = @{
        "Timestamp" = $Timestamp;
        "Level" = $Level;
        "Message" = $Message
    }

    $LogEntry = New-Object -TypeName System.Management.Automation.PSObject -Property $LogEntryProps
    $LogEntry | Select-Object Timestamp, Level, Message | Export-Csv -Path $Path -NoTypeInformation -Append

    if ($PassThru) { Write-Output ($InformationObject | Add-Member @{Level = $Level } -PassThru) }
  }
}

示例用法是

& $PSScriptRoot\Some-Script.ps1 -Param $Param -Verbose *>&1 | Add-LogEntry -Path $LogPath -PassThru

如果您没有捕获变量中的输出或将其传递给管道,那么-PassThru开关本质上应该将信息对象写入控制台。

答案 4 :(得分:-1)

我不得不承认我讨厌PowerShell日志记录和所有Write-*命令......所以我用相同的函数启动所有脚本:

function logto{  ## Outputs data to Folder tree
    Param($D,$P,$F,$C,$filename)
    $LogDebug = $false
    $FDomain =[System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() 
    $SCRdir = $MyInvocation.ScriptName
    $FDNSName = $FDomain.Name 
    $RealFile = $F 
    if($ScriptName -eq $null){
        $ScriptName = "\LogTo\"
    }
    ## if there is a time stamp defined make it part of the directory
    if($GlobalRunTime){ 
        $Flocaldrive = $env:SystemDrive + "\" + $FDNSName + $ScriptName + $GlobalRunTime + "\"
        If ($LogDebug) {Write-host "Set path to $Flocaldrive" -foregroundcolor Magenta}
    }else{
        $Flocaldrive = $env:SystemDrive + "\" + $FDNSName + $ScriptName
        If ($LogDebug) {Write-host "Set path to $Flocaldrive" -foregroundcolor Magenta}
    }
    ## do not write null data
    if ($D -eq $null) {  
        If ($LogDebug) {Write-host "$RealFile :Received Null Data Exiting Function" -foregroundcolor Magenta}
        Return
    }
    ## if no path is chosen default to
    if ($P -eq $null) {  
        $PT = $Flocaldrive
        If ($LogDebug) {Write-host "Path was Null, setting to $PT" -foregroundcolor Magenta}
    }else{
        $PT = $Flocaldrive + $P
        If ($LogDebug) {Write-host "Path detected as $p, setting path to $PT" -foregroundcolor Magenta}
    }
    ## anything with no file goes to Catchall
    If ($RealFile-eq $null) { 
        If ($LogDebug) {Write-host "$D :attempting to write to Null file name, redirected out to Catchall" -foregroundcolor Magenta}
        $RealFile= "\Catchall.txt"
    }
    ##If color is blank DONT write to screen
    if ($C -eq $null) { 
        If ($LogDebug) {Write-host "Color was blank so not writing to screen" -foregroundcolor Magenta}
    }else{
        If ($LogDebug) {Write-host "Attempting to write to console in $C" -foregroundcolor Magenta}
        write-host $D -foregroundcolor $C
    }
    ###### Write standard format
    $DataFile = $PT + $RealFile## define path with File
    ## Check if path Exists if not create it
    If (Test-Path $PT) { 
        If ($LogDebug) {Write-host "$PT :Directory Exists" -foregroundcolor Magenta}
    }else{
        New-Item $PT -type directory | out-null ## if directory does not exist create it
        If ($LogDebug) {Write-host "Creating directory $PT" -foregroundcolor Magenta}
    } 
    ## If file exist if not create it
    If (Test-Path $DataFile) { ## If file does not exist create it
        If ($LogDebug) {Write-host "$DataFile :File Exists" -foregroundcolor Magenta}
    }else{
        New-Item $DataFile -type file | out-null ## if file does not exist create it, we cant append a null file
        If ($LogDebug) {Write-host "$DataFile :File Created" -foregroundcolor Magenta}
    } 
    ## Write our data to file
    $D | out-file -Filepath $DataFile -append  ## Write our data to file
    ## Write to color coded files 
    if ($C -ne $null) { 
        $WriteSumDir = $Flocaldrive + "Log\Sorted" 
        $WriteSumFile = $WriteSumDir + "\Console.txt"
        ## Check if path Exists if not create it
        If (Test-Path $WriteSumDir) { 
            If ($LogDebug) {Write-host "$WriteSumDir :Directory Exists" -foregroundcolor Magenta}
        }else{
            New-Item $WriteSumDir -type directory | out-null ## if directory does not exist create it
            If ($LogDebug) {Write-host "Creating directory $WriteSumDir" -foregroundcolor Magenta}
        }
        ## If file does not exist create it
        If (Test-Path $WriteSumFile) { 
            If ($LogDebug) {Write-host "$WriteSumFile :File Exists" -foregroundcolor Magenta}
        }else{
            New-Item $WriteSumFile -type file | out-null ## if file does not exist create it, we cant append a null file
            If ($LogDebug) {Write-host "$WriteSumFile :File Created" -foregroundcolor Magenta}
        } 
        ## Write our data to file
        $D | out-file -Filepath $WriteSumFile -append ## write everything to same file
        ## Write our data to color coded file
        $WriteColorFile = $WriteSumDir + "\$C.txt"
        If (Test-Path $WriteColorFile) { ## If file does not exist create it
            If ($LogDebug) {Write-host "$WriteColorFile :File Exists" -foregroundcolor Magenta}
        }else{
            New-Item $WriteColorFile -type file | out-null ## if file does not exist create it, we cant append a null file
            If ($LogDebug) {Write-host "$WriteColorFile :File Created" -foregroundcolor Magenta}
        } 
        ## Write our data to Color coded file
        $D | out-file -Filepath $WriteColorFile -append ## write everything to same file
    }
    ## If A return was not specified
    If($filename -ne $null){
        Return $DataFile
    }
}