如何在 powershell 脚本运行时显示 xcopy 的输出



我有这个Powershell脚本,我想将文件从我们的生产服务器复制到我们的测试服务器,并且只让它复制更新或更改的文件。 脚本本身具有更多功能,但此复制是其中之一。 我为此使用以下 XCopy 命令:

... some other scriptingcode
try {
xCopy $source $destination /f /e /r /k /y /d
}
catch {
Write-Host "issue: $PSItem" -ForegroundColor Yellow -BackgroundColor Red
}
... some more other scriptingcode

当我直接从 powershell 提示符运行XCopy命令时,它会显示它的进度、正在复制的文件,同时它正在运行,但是当我从脚本本身运行它时,它不会输出任何内容。只是在最后添加-Verbose只会给我一个脚本错误。

我尝试了以下方法:

$output = $(xCopy $source $destination /f /e /r /k /y /d) -join "`r`n"
Write-Host $output

但这只会在已经复制后给我输出,并且在没有复制 1 或 2 个文件时不是问题,但当您有 100 或 1.000 个文件要复制时,并没有真正的帮助。 仅供参考,-join "'r'n"是因为输出多行时输出是一个数组, 所以我不得不将这些行连接成一个可以通过Write-Host显示的字符串。

我试图用谷歌搜索解决方案,但我想我的谷歌技能不足以让我得到这个问题的任何结果。 那么,有没有办法在执行此操作时XCopy输出,就像直接从 powershell 命令行一样?

不应该看到脚本有问题,因为默认情况下,PowerShell 将外部程序(如xcopy.exe)的 stdout 和 stderr 输出直接传递到主机(控制台),而不会干扰输出或其计时 - 无论你是以交互方式还是从脚本执行程序。

同样,如果您通过管道发送stdout(成功)输出,PowerShell会流式传输它,即在接收时发送每个输出行(可以想象,给定的程序可以在不打印到控制台时发出它们之前本身缓冲行,但xcopy.exe似乎并非如此)。

流式处理方式将外部程序的输出打印到控制台,同时捕获其输出的最简单方法是使用Tee-Objectcmdlet:

# Prints the output to the console as it is being received
# while also capturing it in variable $output
xcopy $source $destination /f /e /r /k /y /d | Tee-Object -Variable output

之后,$output将 stdout 输出作为行数组(或单个字符串,如果恰好只有一个输出行)包含在内。

若要同时捕获stderr输出,请将重定向2>&1附加到外部程序调用;stderr 行被捕获为System.Management.Automation.ErrorRecord实例,就好像它们是PowerShell错误一样;若要在之后将所有数组元素转换为字符串,请使用$output = [string[]] $output

字符编码说明:当 PowerShell 参与处理外部程序输出(在管道中、在变量中捕获、保存到文件)时,输出总是首先根据存储在[Console]::OutputEncoding中的编码解码为 .NET 字符串,在这种情况下,可能需要对其进行修改以匹配给定外部程序使用的特定编码 - 有关详细信息,请参阅此答案。

如果要执行转换或对从外部程序接收的行应用其他格式,请考虑 Cpt.Whale 基于ForEach-Object的有用解决方案。

对于xcopy,您既可以输出到主机,也可以使用foreach捕获输出:

try {
$output = xcopy $src $dst /f /e /r /k /y /d | 
Foreach {
# write line to console
$_ | Write-Host -ForegroundColor Yellow -BackgroundColor Red

# add line to $output
$_
}
}

请注意,xcopy不是 powershell 命令,因此它无法执行某些操作,例如使用默认-verbose-ErrorAction标志。它也不会抛出终止错误,因此除非您正确设置$ErrorActionPreference,否则Try/Catch将不起作用。

这是使用子表达式$()运算符的副作用(请注意,使用组表达式运算符()也会得到相同的效果)。发生这种情况的原因是$()()的功能类似于数学中的括号,并且运算顺序(计算中的技术优先级顺序)决定了内部表达式在外部表达式之前制定。因此,括号内的任何内容必须先完成,然后才能由外部代码处理。

在您这里的情况下,"外部代码"是向控制台显示信息的内容,因此,为什么内部命令必须在显示之前完成。


如果以后不需要评估$output,最简单和性能最高的方法是将命令输出直接传送到Out-Host(您可能还希望重定向错误输出):

# 2>&1 will redirect the error stream (external command STDERR gets written here)
# to the success stream (external command STDOUT gets written here)
# The success stream is what gets passed down the pipeline
xCopy $source $destination /f /e /r /k /y /d 2>&1 | Out-Host

但是,如果您需要同时将$output分配给变量以进行进一步处理,请参阅 @Cpt.Whale 的解决方案涉及巧妙地使用ForEach-Object。他们的方法有一些边缘警告,但确实允许您显示输出,并将输出暂存到另一个变量中以供以后处理,或者在读入输出行时处理输出行。@mklement0的回答还展示了如何使用Tee-Object同时输出到变量和成功流。

最新更新