PowerShell 5 中的写入主机与写入信息
Write-Host vs Write-Information in PowerShell 5
众所周知,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
.[=25 指定消息在控制台中显示的颜色。 =]
另一方面,通过使用Write-Information
,输入消息可以通过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-Information
比Write-Host
好,请在这里留下您的答案。
谢谢。
PowerShell 是关于自动化的。
有时,您 运行 一个脚本一天多次,您不想一直看到输出。
Write-Host
不可能隐藏输出。无论如何,它都会写在控制台上。
使用 Write-Information
,您可以在脚本中指定 -InformationAction
参数。使用此参数,您可以指定是否要查看消息 (-InformationAction Continue
) 或 (-InformationAction SilentlyContinue
)
编辑:
并且请使用 "Some Message" | out-file D:\foo.log
进行记录,而不是 Write-Host
或 Write-Information
Write-*
cmdlet 允许您以结构化方式引导 PowerShell 代码的输出,因此您可以轻松区分不同严重性的消息。
Write-Host
:在控制台上向交互式用户显示消息。与其他 Write-*
cmdlet 不同,此 cmdlet 既不适合也不打算用于 automation/redirection 目的。不是邪恶,只是不同。
Write-Output
:将代码的"normal"输出写入默认(成功)输出流("STDOUT")。
Write-Error
: 将错误信息写入单独的流("STDERR")。
Write-Warning
:将您认为是警告的消息(即不是故障,但用户应该注意的事情)写入单独的流。
Write-Verbose
:将您认为比 "normal" 输出更冗长的信息写入单独的流。
Write-Debug
:将您认为与调试代码相关的信息写入单独的流。
Write-Information
只是这种方法的延续。它允许您在输出中实现日志级别(Debug
、Verbose
、Information
、Warning
、Error
)并且仍然有成功输出流可用于常规输出。
至于为什么 Write-Host
成为 Write-Information
的包装:我不知道这个决定的真正原因,但我怀疑这是因为大多数人不明白 [= =16=] 实际工作,即它可以用来做什么,不应该用来做什么。
据我所知,没有普遍接受或推荐的登录 PowerShell 的方法。例如,您可以实现单个日志记录功能,例如他的回答中建议的 :
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
我不得不承认我讨厌 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
}
}
补充Ansgar's helpful and comprehensive answer:
Write-Host
成为(本质上)
的包装器
Write-Information -InformationAction Continue
在 PSv5 中,大概是因为:
它 启用抑制或重定向 Write-Host
消息,这在以前是不可能的(在 PowerShell 4 或更低版本中,Write-Host
被绕过PowerShell 的流和输出直接到主机),
而 保持向后兼容性 因为消息 默认输出 - 与 Write-Information
不同,其默认行为是 silent(因为它尊重偏好变量 $InformationPreference
,其默认值为 SilentlyContinue
)。
虽然 Write-Host
现在 (PSv5+) 有点用词不当——它不一定再写入 host——它仍然有 与Write-Information
相比有一个明显的优势(如您所述):它可以产生彩色输出 -ForegroundColor
和 -BackgroundColor
.
Ansgar 的回答涵盖了常规日志记录 观点,但 PowerShell 的 Start-Transcript
cmdlet 可以 作为 built-in 备选方案(见下文)。
至于您希望将消息输出到主机同时将它们捕获到日志文件:
PowerShell 的 session 转录本 - 通过 Start-Transcript
和 Stop-Transcript
- 可能给你想要的。
顾名思义,转录本会捕获任何打印到屏幕上的内容(不着色),因此默认情况下包括成功输出,然而
应用于您的示例:
$null = Start-Transcript "D:\foo.log"
$InformationPreference = "Continue"
Write-Information "Some Message"
Write-Information "Another Message"
$null = Stop-Transcript
以上将把消息打印到屏幕和脚本文件;请注意,奇怪的是,只有在 文件中 才会以 INFO:
.
作为前缀
(相比之下,Write-Warning
、Write-Verbose
和 Write-Debug
- 如果配置为生成输出 - 使用前缀 WARNING:
、VERBOSE:
、DEBUG:
on-screen 并在文件中;类似地,Write-Error
在 on-screen 和文件中生成 "noisy" 多行输入。)
注意一个 bug 只影响 Windows PowerShell(它有been fixed in PowerShell [Core].Thanks, JohnLBevan.): 来自 Write-Information
的输出显示在转录文件中(但不在屏幕上),即使 $InformationPreference
设置为 SilentlyContinue
(默认);排除 Write-Information
输出(通过首选项变量或 -InformationAction
参数)的唯一方法似乎是 Ignore
的值 - 这绝对使输出静音 - 或者,奇怪的是,Continue
,其中它只打印到 控制台 ,正如 PetSerAl 指出的那样。
简而言之,你可以使用 Start-Transcript
作为一个方便的,built-in 近似 的日志工具,它的冗长你可以通过首选项变量($InformationPreference
、$VerbosePreference
、...) 从外部控制,与传统日志记录有以下 重要差异 :
一般来说,进入转录文件的内容是也输出到控制台(通常可以认为一个加号)。
但是,成功输出(数据输出)默认情况下也发送到成绩单——除非你捕获它或完全抑制它——你不能有选择地将它排除在文字记录之外:
如果捕获或抑制它,它也不会显示在主机(默认情况下,控制台)中[1].
然而,相反的情况是可能的:您可以通过 Out-Default -Transcript
谢谢,PetSerAl;例如,
'to transcript only' | Out-Default -Transcript
;但是,从 PowerShell 7.0 开始,这似乎会在记录中记录输出 twice;另请注意 Out-Default
通常不意味着从用户代码中调用 - 请参阅 this answer.
通常,外部重定向(将>
应用于对内部执行转录的脚本的调用)保持流出成绩单 ,有两个 例外 ,从 PowerShell 7.0 开始:
Write-Host
输出,即使使用 6>
或 *>
重定向。
- 错误输出,即使使用
2>
或 *>
重定向。
然而,使用 $ErrorActionPreference = 'SilentlyContinue'
/ 'Ignore'
确实 将 non-terminating 错误排除在转录之外,但不是 终止个。
脚本文件不是 line-oriented(有一个包含调用信息的 header 行块,并且不能保证脚本产生的输出仅限于一行),因此您不能期望以 line-by-line 方式解析它们。
[1] PetSerAl 提到以下有限且有些麻烦的解决方法 (PSv5+)仅将成功输出结束到控制台,这特别排除了通过管道发送输出或捕获输出:
'to console only' | Out-String -Stream | ForEach-Object { $Host.UI.WriteLine($_) }
这是我最近在我的脚本中使用的更专业的日志记录函数的通用版本。
这种情况是,当我需要将某些事情作为计划任务执行时,我通常会创建一个通用脚本,或者在执行 "heavy lifting" 的模块中运行,然后是一个处理细节的调用脚本对于特定的工作,例如从 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 开关实质上应该将信息对象写入控制台。
众所周知,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
.[=25 指定消息在控制台中显示的颜色。 =]
另一方面,通过使用Write-Information
,输入消息可以通过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-Information
比Write-Host
好,请在这里留下您的答案。
谢谢。
PowerShell 是关于自动化的。
有时,您 运行 一个脚本一天多次,您不想一直看到输出。
Write-Host
不可能隐藏输出。无论如何,它都会写在控制台上。
使用 Write-Information
,您可以在脚本中指定 -InformationAction
参数。使用此参数,您可以指定是否要查看消息 (-InformationAction Continue
) 或 (-InformationAction SilentlyContinue
)
编辑:
并且请使用 "Some Message" | out-file D:\foo.log
进行记录,而不是 Write-Host
或 Write-Information
Write-*
cmdlet 允许您以结构化方式引导 PowerShell 代码的输出,因此您可以轻松区分不同严重性的消息。
Write-Host
:在控制台上向交互式用户显示消息。与其他Write-*
cmdlet 不同,此 cmdlet 既不适合也不打算用于 automation/redirection 目的。不是邪恶,只是不同。Write-Output
:将代码的"normal"输出写入默认(成功)输出流("STDOUT")。Write-Error
: 将错误信息写入单独的流("STDERR")。Write-Warning
:将您认为是警告的消息(即不是故障,但用户应该注意的事情)写入单独的流。Write-Verbose
:将您认为比 "normal" 输出更冗长的信息写入单独的流。Write-Debug
:将您认为与调试代码相关的信息写入单独的流。
Write-Information
只是这种方法的延续。它允许您在输出中实现日志级别(Debug
、Verbose
、Information
、Warning
、Error
)并且仍然有成功输出流可用于常规输出。
至于为什么 Write-Host
成为 Write-Information
的包装:我不知道这个决定的真正原因,但我怀疑这是因为大多数人不明白 [= =16=] 实际工作,即它可以用来做什么,不应该用来做什么。
据我所知,没有普遍接受或推荐的登录 PowerShell 的方法。例如,您可以实现单个日志记录功能,例如他的回答中建议的
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
我不得不承认我讨厌 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
}
}
补充Ansgar's helpful and comprehensive answer:
Write-Host
成为(本质上)
的包装器
Write-Information -InformationAction Continue
在 PSv5 中,大概是因为:
它 启用抑制或重定向
Write-Host
消息,这在以前是不可能的(在 PowerShell 4 或更低版本中,Write-Host
被绕过PowerShell 的流和输出直接到主机),而 保持向后兼容性 因为消息 默认输出 - 与
Write-Information
不同,其默认行为是 silent(因为它尊重偏好变量$InformationPreference
,其默认值为SilentlyContinue
)。
虽然 Write-Host
现在 (PSv5+) 有点用词不当——它不一定再写入 host——它仍然有 与Write-Information
相比有一个明显的优势(如您所述):它可以产生彩色输出 -ForegroundColor
和 -BackgroundColor
.
Ansgar 的回答涵盖了常规日志记录 观点,但 PowerShell 的 Start-Transcript
cmdlet 可以 作为 built-in 备选方案(见下文)。
至于您希望将消息输出到主机同时将它们捕获到日志文件:
PowerShell 的 session 转录本 - 通过 Start-Transcript
和 Stop-Transcript
- 可能给你想要的。
顾名思义,转录本会捕获任何打印到屏幕上的内容(不着色),因此默认情况下包括成功输出,然而
应用于您的示例:
$null = Start-Transcript "D:\foo.log"
$InformationPreference = "Continue"
Write-Information "Some Message"
Write-Information "Another Message"
$null = Stop-Transcript
以上将把消息打印到屏幕和脚本文件;请注意,奇怪的是,只有在 文件中 才会以 INFO:
.
作为前缀
(相比之下,Write-Warning
、Write-Verbose
和 Write-Debug
- 如果配置为生成输出 - 使用前缀 WARNING:
、VERBOSE:
、DEBUG:
on-screen 并在文件中;类似地,Write-Error
在 on-screen 和文件中生成 "noisy" 多行输入。)
注意一个 bug 只影响 Windows PowerShell(它有been fixed in PowerShell [Core].Thanks, JohnLBevan.): 来自 Write-Information
的输出显示在转录文件中(但不在屏幕上),即使 $InformationPreference
设置为 SilentlyContinue
(默认);排除 Write-Information
输出(通过首选项变量或 -InformationAction
参数)的唯一方法似乎是 Ignore
的值 - 这绝对使输出静音 - 或者,奇怪的是,Continue
,其中它只打印到 控制台 ,正如 PetSerAl 指出的那样。
简而言之,你可以使用 Start-Transcript
作为一个方便的,built-in 近似 的日志工具,它的冗长你可以通过首选项变量($InformationPreference
、$VerbosePreference
、...) 从外部控制,与传统日志记录有以下 重要差异 :
一般来说,进入转录文件的内容是也输出到控制台(通常可以认为一个加号)。
但是,成功输出(数据输出)默认情况下也发送到成绩单——除非你捕获它或完全抑制它——你不能有选择地将它排除在文字记录之外:
如果捕获或抑制它,它也不会显示在主机(默认情况下,控制台)中[1].
然而,相反的情况是可能的:您可以通过
Out-Default -Transcript
谢谢,PetSerAl;例如,
'to transcript only' | Out-Default -Transcript
;但是,从 PowerShell 7.0 开始,这似乎会在记录中记录输出 twice;另请注意Out-Default
通常不意味着从用户代码中调用 - 请参阅 this answer.
通常,外部重定向(将
>
应用于对内部执行转录的脚本的调用)保持流出成绩单 ,有两个 例外 ,从 PowerShell 7.0 开始:Write-Host
输出,即使使用6>
或*>
重定向。- 错误输出,即使使用
2>
或*>
重定向。
然而,使用$ErrorActionPreference = 'SilentlyContinue'
/'Ignore'
确实 将 non-terminating 错误排除在转录之外,但不是 终止个。
脚本文件不是 line-oriented(有一个包含调用信息的 header 行块,并且不能保证脚本产生的输出仅限于一行),因此您不能期望以 line-by-line 方式解析它们。
[1] PetSerAl 提到以下有限且有些麻烦的解决方法 (PSv5+)仅将成功输出结束到控制台,这特别排除了通过管道发送输出或捕获输出:
'to console only' | Out-String -Stream | ForEach-Object { $Host.UI.WriteLine($_) }
这是我最近在我的脚本中使用的更专业的日志记录函数的通用版本。
这种情况是,当我需要将某些事情作为计划任务执行时,我通常会创建一个通用脚本,或者在执行 "heavy lifting" 的模块中运行,然后是一个处理细节的调用脚本对于特定的工作,例如从 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 开关实质上应该将信息对象写入控制台。