「脚本确实在运行,但一旦失败就不知道发生了什么」── 这是投入运维的 PowerShell 脚本中最常见的咨询。追查原因,往往会发现同一种结构:处理状况只用 Write-Host 来表达,而在没有人盯着屏幕的夜间执行中,什么都没有留下。
PowerShell 有 6 种输出流,各自面向的「读者」不同:是要返回值,还是要给人看,还是只在排查时才需要。带着这样的意识区分书写,就能让同一个脚本在交互执行时表现得友好,在无人值守执行时表现得机器可读。反之,如果把所有内容都塞进 Write-Host,只会增加既不能当值使用、也不会留在日志里的信息。
本文将整理 6 种输出流各自的角色、Write-Host 的正确定位、函数返回值被污染的机制、由调用方控制详细程度的方法,以及可用于运维的结构化日志形式。
1. 先说结论
- PowerShell 的输出流共有 6 种。分别是成功(1)、错误(2)、警告(3)、详细(4)、调试(5)、信息(6),各自都能用编号进行重定向。
*>代表全部输出流。1 - PowerShell 5.0 及以后,
Write-Host会写入信息流。因此可以用6>重定向,或用-InformationVariable捕获。在此之前既无法捕获,也无法抑制。2 Write-Host专门用于「展示给人看」。不能用于返回值的场景。要传给管道的值应使用Write-Output(或裸输出)。23- 函数会返回内部输出的所有对象。与是否使用
return无关。不需要的输出,惯常做法是用$null = ...丢弃。4 - 进度用
Write-Progress,处理经过用Write-Verbose。进度显示不是可以重定向的数据流。5 - 加上
[CmdletBinding()]的函数会自动获得-Verbose、-Debug、-InformationAction等通用参数。把详细程度的决定权交给调用方才是正确做法。67 - 默认值值得记住。
$VerbosePreference、$DebugPreference、$InformationPreference为 SilentlyContinue,$WarningPreference、$ErrorActionPreference为 Continue。8 - 想事后解析的日志要结构化(1 行 1 个 JSON)。想还原屏幕显示效果的话,可以并用
Start-Transcript。9
本文的前提版本与 5.1 中的差异
在信息系统部门的现场,Windows PowerShell 5.1 依然是主流。本文先整理清楚各处描述分别以哪个版本为前提。
| 描述 | 前提版本 | 在 Windows PowerShell 5.1 中 |
|---|---|---|
6 种输出流及带编号的重定向、*> |
无论 5.1 还是 PowerShell 7 都相同 | 原样适用1 |
Write-Host 写入信息流(6 号)(可用 6> 或 -InformationVariable 捕获) |
PowerShell 5.0 及以后(第 3 章) | 5.1 属于 5.0 及以后的版本,原样适用2 |
Write-Information 与 -InformationAction / -InformationVariable |
PowerShell 5.0 及以后(第 4 章) | 原样适用7 |
函数返回内部的全部输出、用 $null = ... 抑制 |
与版本无关(第 5 章) | 原样适用4 |
外部命令(原生命令)的 2>&1 处理方式 |
按 PowerShell 7.4 及以后的行为说明(第 6 章) | 不适用。用类型来区分外部命令输出的写法,请务必在实际运行的版本上确认 |
用 -ProgressAction 通用参数控制 Write-Progress |
PowerShell 7.4 及以后5 | 无法使用。请用 $ProgressPreference 控制(第 8 章按此方式撰写) |
| 文末分发的示例代码 | 在 PowerShell 7.6 中实际运行验证(Pester 14 项) | ─ |
简而言之,第 2 章至第 5 章及第 7 章在 5.1 中同样可以原样使用。需要注意版本差异的,仅有外部命令的重定向(第 6 章)与进度显示的控制方式(第 8 章)这两处。
2. 六种输出流及其去向
首先用一张图梳理清楚,哪个写入命令的内容会送达何处。
flowchart LR
O["Write-Output / 裸输出"]
OTH["Write-Error / Write-Warning<br/>Write-Verbose / Write-Debug<br/>Write-Information / Write-Host"]
S1["1 成功流"]
S26["2 错误 / 3 警告 / 4 详细<br/>5 调试 / 6 信息"]
PIPE["传递给后续处理<br/>管道・赋值给变量"]
HOST["显示在屏幕上<br/>是否默认显示取决于环境设置变量"]
FILE["可保存到文件<br/>带编号的重定向・用于捕获的变量"]
O --> S1
OTH --> S26
S1 --> PIPE
S1 --> FILE
S26 --> HOST
S26 --> FILE
只有成功流会传递给后续处理。其余 5 种要么显示在屏幕上,要么通过重定向或 -*Variable 捕获,都不会进入管道。如果在左侧的分支(用哪个命令写)上选错,一定会以「值传不到」或「日志里没留下」的形式表现出来。编号用于像 3> warnings.log 这样指定重定向时使用(第 6 章)。
| # | 流 | 写入命令 | 目标读者 | 默认环境设置 |
|---|---|---|---|---|
| 1 | 成功(Success) | Write-Output / 裸输出 |
后续处理(管道) | ─ |
| 2 | 错误(Error) | Write-Error / 抛出 |
人 + 监控 | Continue |
| 3 | 警告(Warning) | Write-Warning |
人 | Continue |
| 4 | 详细(Verbose) | Write-Verbose |
正在排查的人 | SilentlyContinue |
| 5 | 调试(Debug) | Write-Debug |
开发者 | SilentlyContinue |
| 6 | 信息(Information) | Write-Information / Write-Host |
人 + 留存记录 | SilentlyContinue |
信息流的默认值是 SilentlyContinue,但 Write-Host 却会显示在屏幕上,这并不矛盾。只有 Write-Host 是例外,Microsoft Learn 明确写道:「$InformationPreference 环境设置变量与 -InformationAction 通用参数不会影响 Write-Host 的消息」。2 也就是说,Write-Information 默认不会显示,而 Write-Host 即使在默认情况下也会显示,能够抑制它的只有 -InformationAction Ignore 与 6> 重定向。
这张表中最重要的是第一行。成功流不是用来写「给人看的消息」的地方。如果在这里写入面向人的字符串,那么一旦用 | 把该函数接入管道,意料之外的字符串就会流入后续处理。
function Get-KsTargetFile {
Write-Output "正在检索目标..." # 【NG】混入返回值
Get-ChildItem -Path $path -Filter '*.csv'
}
# 调用方期望得到的是 FileInfo 数组,结果开头却混入了一个字符串
$files = Get-KsTargetFile
$files[0].FullName # → 空(因为开头是字符串)
正确的做法是,把进展报告发送到详细流或信息流。
function Get-KsTargetFile {
[CmdletBinding()]
param([string] $Path)
Write-Verbose "正在检索目标: $Path" # 仅在指定 -Verbose 时显示
Get-ChildItem -Path $Path -Filter '*.csv' # 返回值只有 FileInfo
}
3. Write-Host 是「恶」吗
曾经有一种「不要用 Write-Host」的说法广为流传,但如今的 PowerShell 情况已经不同了。PowerShell 5.0 及以后,Write-Host 被实现为向信息流(6 号)写入,可以用 6> 重定向,或用 -InformationVariable 捕获。2 「只能显示在屏幕上,事后完全无法获取」这种当年的批评已经不再适用。不过正如第 2 章所提到的,即便信息流的默认值是 SilentlyContinue,Write-Host 的显示也不会消失,因为它不受 $InformationPreference 与 -InformationAction 的影响(唯一的例外是 -InformationAction Ignore,只有它能抑制 Write-Host 的输出)。「变得可以捕获」与「默认显示在屏幕上」这两点是可以同时成立的。2
话虽如此,它的适用场景仍然有限。
适合使用 Write-Host 的场景
- 在交互式使用的工具中,想输出带颜色的标题或分隔符时(
-ForegroundColor) - 想向用户说明「这个脚本接下来要做什么」时
- 目的不是处理的值,而是经过装饰的显示本身时
不应使用 Write-Host 的场景
- 想把值作为函数的返回值传递时(→
Write-Output) - 想留存日后要解析的运维日志时(→ 结构化日志,或
Write-Information) - 想由调用方切换显示・不显示时(→
Write-Verbose)
无人值守执行的脚本,本来就没有显示对象。只用 Write-Host 来表达状况的脚本,一旦被任务计划程序执行,就会立刻变成一个「什么都看不出来的脚本」。这正是本文标题的含义所在。
4. 把控制权交给调用方 ── [CmdletBinding()] 与通用参数
Write-Verbose 的真正价值在于,是否显示可以由调用方决定。只需给函数加上 [CmdletBinding()],就能自动获得 -Verbose、-Debug、-WarningAction、-InformationAction、-ErrorAction 等通用参数。67
function Invoke-KsImport {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $CsvPath
)
Write-Verbose "开始导入: $CsvPath" # 默认不显示
Write-Information "导入: $CsvPath" -Tags 'KsImport' # 默认不显示(可以捕获)
$rows = Import-Csv -Path $CsvPath
if ($rows.Count -eq 0) {
Write-Warning "$CsvPath 中没有可导入的对象" # 默认会显示
return
}
Write-Verbose "将处理 $($rows.Count) 条记录"
$rows | ForEach-Object { ConvertTo-KsRecord $_ } # 返回值仅此而已
}
# 通常执行: 只显示警告,返回值是记录
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv'
# 排查时: 也想看到经过
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -Verbose
# 只把信息流捕获到变量,之后写入日志文件
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -InformationVariable info
$info | ForEach-Object { $_.MessageData } | Add-Content -Path $logPath
经常能见到自建 $LogLevel 变量、再用 if 语句分支的实现,但采用标准机制会更简短,也更能让他人理解意图。因为「加上 -Verbose 就会显示详细信息」是所有使用 PowerShell 的人的共同认知。
另外,$VerbosePreference 等环境设置变量会作用于当前作用域及其子作用域。8 用 -Verbose 调用函数时,函数内部调用的 cmdlet 也会开始输出详细信息,因此有时输出量会比预想的更多。
5. 函数返回值被污染 ── PowerShell 特有的陷阱
PowerShell 的函数即使没有显式的 return,也会返回内部输出的所有对象。4 这既是一项强大的特性,同时也是最容易出事故的部分。
function New-KsWorkFolder {
param([string] $Path)
New-Item -Path $Path -ItemType Directory # 【陷阱】DirectoryInfo 混入返回值
$list = [System.Collections.Generic.List[string]]::new()
$list.Add('log') # 【陷阱2】.Add() 是 void,没有实际影响
$sb = [System.Text.StringBuilder]::new()
$sb.Append('x') # 【陷阱3】StringBuilder 自身被返回
return $Path
}
$p = New-KsWorkFolder -Path 'D:\work' # $p 会变成一个含 3 个元素的数组(DirectoryInfo、StringBuilder、string)
应对方法是「丢弃不需要的输出」。写法有三种,$null = ... 最为轻量。
$null = New-Item -Path $Path -ItemType Directory # 推荐
New-Item -Path $Path -ItemType Directory | Out-Null # 多了管道,会稍慢一些
[void] $sb.Append('x') # 常用于 .NET 方法
只要写测试,这种行为一下子就能发现。「固定返回值的形态」这类测试的重要性,在《用 Pester 完善 PowerShell 测试》中有讨论。
6. 重定向与捕获
输出流可以按编号分别重定向。1
.\Invoke-NightlyExport.ps1 3> warnings.log # 只把警告输出到单独的文件
.\Invoke-NightlyExport.ps1 4>&1 | Tee-Object -FilePath run.log # 把详细信息合流到成功流
.\Invoke-NightlyExport.ps1 *> all.log # 把全部输出流汇总到一个文件
.\Invoke-NightlyExport.ps1 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }
> 是覆盖写入,>> 是追加写入。不过要注意,用重定向合流后,类型会混在一起。像上面的例子那样,把 PowerShell 脚本或函数的错误流合流时,其元素仍然是 ErrorRecord,因此可以像上面那样按类型区分。
而外部程序(原生命令)的 2>&1 情况则不同。PowerShell 7.4 及以后,重定向输出会被当作字节流处理,合流后会变成字符串,因此按 ErrorRecord 区分不再有效。如果想区分外部命令的 stdout/stderr,请不要合流,分开接收(参见《正确从 PowerShell 调用外部 exe》)。
代码是否真的按预期分流,最可靠的方法是自己动手试一次。用一段同时输出成功流和警告流的简短代码就能确认。
# 只把警告输出到单独的文件。屏幕上只会留下 '数据'
& { Write-Output '数据'; Write-Warning '注意事项' } 3> warnings.log
Get-Content warnings.log # 里面是警告消息,不会包含 '数据'
# 把全部输出流汇总到一个文件
& { Write-Output '数据'; Write-Warning '注意事项'; Write-Verbose '详细信息' -Verbose } *> all.log
Get-Content all.log # 3 项输出会一起写入
如果加了 3> 而 warnings.log 仍是空的,说明那条消息并没有写入警告流(如果是用 Write-Host 写的,就要用 6>)。排查「本该输出到日志里的内容却没有出现」这类问题,从这两行开始是最快的。
另外,*> 虽然省事,但一旦汇总到一起,之后再想按流重新拆分就会变得困难。如果想用程序做机械化统计,请按流分别输出到不同文件,或者采用下一章介绍的结构化日志。
如果想把执行证据原样整体保留下来,Start-Transcript 很省事。它会把会话的命令与输出记录成文本,事后就能还原「当时屏幕上显示了什么」。9
Start-Transcript -Path "C:\Logs\export_$(Get-Date -f yyyyMMdd_HHmmss).log" -Append
try { Invoke-KsExport }
finally { Stop-Transcript }
7. 做成可以事后解析的日志 ── 结构化日志
给人看的日志,和给机器统计的日志是两码事。当你想知道「上个月这个错误出现了多少次」时,自由格式的文本就会变成和 grep 的耐力赛。如果按 1 行 1 个 JSON(JSON Lines)来写,统计单靠 PowerShell 就能完成。
function Write-KsLog {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [ValidateSet('INFO','WARN','ERROR')] [string] $Level,
[Parameter(Mandatory)] [string] $Message,
[hashtable] $Data,
[string] $Path = $script:KsLogPath
)
$entry = [ordered]@{
ts = (Get-Date).ToString('o') # ISO 8601,便于排序与比对
level = $Level
message = $Message
script = $MyInvocation.ScriptName
host = $env:COMPUTERNAME
user = $env:USERNAME
}
# 附加信息放进 data 之下。如果混在顶层,一旦调用方传入了
# level 或 user 之类的键,就会覆盖基本项目
if ($Data) { $entry['data'] = $Data }
# 用 -Compress 压成 1 行。追加写入用 Add-Content(UTF-8)
$entry | ConvertTo-Json -Compress -Depth 5 | Add-Content -Path $Path -Encoding utf8
# 面向人的显示走标准机制(即使显示在屏幕上,也不会返回值)
switch ($Level) {
# 不指定 -ErrorAction。如果在这里固定下来,调用方就无法
# 通过 -ErrorAction Stop 把它变成终止错误
'ERROR' { Write-Error $Message }
'WARN' { Write-Warning $Message }
default { Write-Verbose $Message }
}
}
# 使用示例
Write-KsLog -Level INFO -Message '导入完成' -Data @{ rows = 1250; file = 'orders.csv'; ms = 4210 }
# → {"ts":"...","level":"INFO","message":"导入完成","script":"...","host":"...","user":"...",
# "data":{"rows":1250,"file":"orders.csv","ms":4210}}
统计代码如下。
Get-Content 'C:\Logs\ks.log' |
ForEach-Object { $_ | ConvertFrom-Json } |
Where-Object { $_.level -eq 'ERROR' -and [datetime]$_.ts -ge (Get-Date).AddDays(-30) } |
Group-Object message | Sort-Object Count -Descending | Select-Object Count, Name
也可以选择接入 Windows 的标准日志基础设施(事件日志・ETW)。如果考虑与监控工具联动,或从多台机器统一收集,那种方式更有优势。设计上的比较整理在《Windows 事件日志・ETW 与结构化日志》中,日志文件的世代管理整理在《PowerShell 脚本应用 ── 日志排查・归档・报表化》中。
8. 进度显示的处理
Write-Progress 使用的是宿主的进度显示功能,并不是可以重定向的数据流。5 也就是说无法留存到日志中。无人值守执行时既没有显示对象,视环境不同,进度更新的开销有时也不可忽视。
# 在无人值守脚本的开头停止进度显示
$ProgressPreference = 'SilentlyContinue'
如果想把进度留存到运维日志中,实用的做法是只把节点信息写入详细流。
$i = 0
foreach ($row in $rows) {
$i++
if ($i % 100 -eq 0) { Write-Verbose "已完成 $i / $($rows.Count) 件" }
...
}
9. 实务定式(判断表)
| 想输出的信息 | 使用的方式 | 理由 |
|---|---|---|
| 传给后续处理的值 | Write-Output / 裸输出 |
成功流专用于数据3 |
| 处理经过(只在排查时想看) | Write-Verbose |
由调用方通过 -Verbose 控制7 |
| 想作为运维记录的事件 | Write-Information + 结构化日志 |
可用 -InformationVariable 捕获2 |
| 交互式工具的装饰性显示 | Write-Host |
经由信息流,因此也可以捕获2 |
| 在预期之内但需要注意的事件 | Write-Warning |
默认会显示,可用 -WarningVariable 捕获8 |
| 失败 | Write-Error / throw |
错误处理请参见专门文章 |
| 开发中的内部状态 | Write-Debug |
仅在加了 -Debug 时7 |
| 进度 | Write-Progress(仅限交互时) |
不会留存到日志中。无人值守执行时应停止5 |
| 整体保留执行证据 | Start-Transcript |
作为自建日志的保险并用9 |
10. 总结
- PowerShell 的输出分为 6 种流。成功流专用于数据,混入面向人的消息会破坏返回值。
- PowerShell 5.0 及以后的
Write-Host因写入信息流而可以被捕获,但不能用于返回值的场景,也不能用于运维日志的场景。 - 函数会返回内部输出的全部内容。不需要的输出,惯常做法是用
$null = ...丢弃。 - 加上
[CmdletBinding()]并使用Write-Verbose/Write-Information,就能把详细程度的控制权交给调用方。这比自建日志级别变量更简短,也更能传达意图。 - 想事后统计的日志,要做成 1 行 1 个 JSON 的结构化日志。如果目的是还原屏幕显示,可以并用
Start-Transcript。 - 进度显示不是数据流,因此不会留存到日志中。无人值守执行时,用
$ProgressPreference = 'SilentlyContinue'停止它是实用的做法。
示例代码下载
本文涉及的代码已整理成可以直接运行的形式提供下载。其中包含 1 行 1 个 JSON 的结构化日志,以及不污染返回值的函数写法。
本文的示例已经在 PowerShell 7.6 中实际运行并验证过(Pester 14 项)。运行 zip 中包含的 Invoke-SampleTests.ps1,你也可以在自己的环境中重现相同的验证。
# 语法解析 + 静态分析 + Pester 测试
./Invoke-SampleTests.ps1
配置值(路径、服务器名、租户 ID 等)均为示例。请勿直接在生产环境中运行,请根据贵公司的环境自行替换。
相关文章
- PowerShell 的错误处理与重试设计 ── 从 try/catch 失效的陷阱到 exit code、重试的实务定式
- PowerShell 脚本的参数设计与模块化 ── 从「能运行的脚本」到「可以交给别人的脚本」
- PowerShell 脚本进阶 ── 安全地实现日志排查、归档与报表自动化
- Windows 事件日志・ETW 入门 ── 让业务应用程序的日志接入操作系统标准机制
- 用 Pester 完善 PowerShell 测试 ── 让运维脚本不易损坏的实务方法
- 自建日志组件的最低要求与集成测试清单
相关咨询领域
合同会社小村软件提供运维脚本日志设计的复查、消除「不知道失败原因」的状态,以及整备可连接监控与统计的日志基础设施等服务。
参考链接
-
Microsoft Learn,about_Redirection。关于 PowerShell 拥有成功・错误・警告・详细・调试・信息各个输出流并分别用编号标识,用
>>>重定向到文件,用n>&1合流到其他流,以及用*>重定向全部流。 ↩ ↩2 ↩3 -
Microsoft Learn,Write-Host。关于 PowerShell 5.0 及以后的 Write-Host 作为 Write-Information 的包装器向信息流输出,从而可以用 6> 重定向;$InformationPreference 环境设置变量与 -InformationAction 通用参数不会影响 Write-Host 的消息(例外是 -InformationAction Ignore),因此默认也会显示在屏幕上;-ForegroundColor / -BackgroundColor 的装饰效果;以及输出不会传递给管道。相关地,Write-Information 涉及向信息流的显式写入与用 -Tags 进行分类。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn,Write-Output。关于把对象发送到成功流(管道),以及即使不显式调用,表达式的结果也会以同样方式输出。 ↩ ↩2
-
Microsoft Learn,about_Return。关于 PowerShell 的函数无论是否使用 return,都会把函数内输出的所有对象返回给调用方,以及 return 是一种在返回值的同时退出当前作用域的语法。 ↩ ↩2 ↩3
-
Microsoft Learn,Write-Progress。关于把命令的进度作为宿主的进度显示输出,可以用 $ProgressPreference 控制显示,以及 PowerShell 7.4 及以后也可以用 -ProgressAction 通用参数控制。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn,about_Functions_CmdletBindingAttribute。关于加上 [CmdletBinding()] 属性的高级函数会像已编译的 cmdlet 一样运作,并自动获得通用参数。 ↩ ↩2
-
Microsoft Learn,about_CommonParameters。关于 -Verbose / -Debug / -WarningAction / -InformationAction / -ErrorAction 及对应的 -*Variable 参数的行为,以及与环境设置变量的关系。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn,about_Preference_Variables。关于 $VerbosePreference・$DebugPreference・$InformationPreference 的默认值为 SilentlyContinue,$WarningPreference 与 $ErrorActionPreference 的默认值为 Continue,用 $ProgressPreference 控制进度显示,以及这些设置作用于当前作用域及其子作用域。 ↩ ↩2 ↩3
-
Microsoft Learn,Start-Transcript。关于把会话的命令与控制台输出记录到文本文件,用 -Append 追加写入,以及用 Stop-Transcript 停止记录。 ↩ ↩2 ↩3
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
PowerShell脚本运行慢时该看哪里 ── 数组・管道・匹配的诀窍
整理 PowerShell 脚本变慢的常见原因。从数组 += 导致 O(n^2) 的原理、管道与 foreach 的差异、匹配的哈希表化、文件 I/O 的改善,到正确的测量方法,从实务角度进行讲解。
PowerShell 的并行处理 ── ForEach-Object -Parallel 与作业(Job)的选用之道
本文从实务角度整理 ForEach-Object -Parallel、Start-ThreadJob、Start-Job 的区别与选用之道,$using: 与线程安全性,ThrottleLimit 的确定方法,以及反而会变慢的情形。
从 PowerShell 正确调用外部 exe ── 参数引用、退出代码与乱码的陷阱
从 PowerShell 调用 robocopy 或公司内部 EXE 时,参数会损坏、拿不到退出代码、输出还会出现乱码。本文从实务角度整理 PowerShell 7.3 的参数传递变更、停止解析令牌 --%、以及 Start-Process 的使用区分。
PowerShell 中凭据的安全处理方式 ── 把明文密码逐出脚本
本文整理将 PowerShell 脚本中的明文密码迁移到安全存储的步骤。讲解 SecureString 的真实面貌与局限、Export-Clixml 基于 DPAPI 保存的原理,直至 SecretManagement/SecretStore 的适用场景。
PowerShell 脚本的参数设计与模块化 ── 从「能运行的脚本」到「可以交给别人的脚本」
本文整理把 PowerShell 脚本提升到可以交给他人使用的品质的具体步骤,讲解 param 块与 [CmdletBinding()]、输入验证、管道输入、-WhatIf 支持、.psm1 模块化,直至内部共享与 Git 管理的要点。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
Windows 应用程序开发
支持包含常驻处理、设备联动、运行日志与可维护结构的 Windows 桌面应用程序。
技术咨询 & 设计评审
协助梳理设计方向、架构边界、生命周期责任,以及既有 Windows 资产的处理方式。
常见问题
汇总了咨询这一主题时常见的问题。
- 不能使用 Write-Host 吗?
- 准确地说,并非禁止使用,而是用途有限。PowerShell 5.0 及以后的 Write-Host 会写入信息流(6 号),因此可以通过 6> 重定向或 -InformationVariable 捕获。不过它是一个默认始终显示在屏幕上的命令,无法用于将值传入管道。归纳一下:面向交互式工具、供人阅读的装饰性显示用 Write-Host;处理进度或补充信息用 Write-Verbose 或 Write-Information;要传给后续处理的值用 Write-Output(或裸输出)。
- 函数的返回值中混入了非预期的值。
- 这是因为 PowerShell 的函数即使没有显式的 return,也会返回函数内部输出的所有对象。像 New-Item 或 StringBuilder 的 .Append() 这类会返回值的命令与方法,如果不加处理地直接调用,其返回值就会流入成功流并到达调用方。反过来,像 List[T] 的 .Add() 这样返回值为 void 的方法不会输出任何内容,因此不需要抑制。应对方法是用 $null = ... 丢弃不需要的输出,或者加上 | Out-Null,或者用 [void] 进行类型转换。从性能角度看,$null = ... 是最轻量的写法。
- 希望在运行时切换脚本的详细日志。
- 请用 Write-Verbose 写出处理过程,并给函数加上 [CmdletBinding()]。这样一来,只有当调用方指定 -Verbose 时才会显示。如果想让它始终显示,可以在脚本开头设置 $VerbosePreference = 'Continue'。同样,Write-Debug 可以通过 -Debug、Write-Warning 可以通过 -WarningAction 由调用方控制。与自建日志级别变量相比,采用 PowerShell 的标准机制更简短,也更能让他人读懂意图。
- 包括详细日志与警告在内,如何把全部内容都保存到文件中?
- 根据用途不同有三种方法。如果只是想简单地把全部输出流落到文件,用 *> 重定向即可。如果想把执行证据(屏幕上显示的内容)原样保留下来,用 Start-Transcript 最省事。如果之后想通过程序解析,用自己的日志函数写出 1 行 1 个 JSON 的结构化日志最可靠,即便在这种情况下,把 transcript 作为保险并用也是有价值的。
- Write-Progress 的显示内容能保存到日志文件里吗?
- 不能。因为进度显示是宿主的显示功能,与可重定向的数据流是分开处理的。无人值守执行时没有显示对象,所以请把进度和需要留存到日志中的信息区分开来考虑。在无人值守执行时,把 $ProgressPreference 设为 'SilentlyContinue' 以停止进度显示本身,视环境不同,有时还能带来明显的速度提升。如果想把进度留存到日志中,更实用的做法是用 Write-Verbose 只记录「100 件中已完成 50 件」这类节点信息。