这是一个 self 回答问题。我将仅添加针对 PowerShell 7.3 或更高版本的答案。请随意添加任何以前的 PowerShell 版本的答案。
我有一个自定义日志记录函数,它接受管道输入并使用可步进管道对象将其输入正确转发到Out-File
。 This answer提供了在这种情况下需要步进管道的背景。另请参阅博客文章 Mastering the (steppable) pipeline .
当我的日志记录功能所属的管道的任何命令抛出异常(又名脚本终止错误)并且异常被捕获时,文件由 Out-File
打开的文件不会关闭,并且任何后续向文件添加内容的尝试(例如使用 Add-Content
)都会失败,并显示错误消息:
The process cannot access the file 'C:\LogFile.log' because it is being used by another process.
这是一个可重现的示例:
$LOG_FILE_PATH = Join-Path $PSScriptRoot LogFile.log
Function Test-SteppablePipeline {
[CmdletBinding()]
param (
[Parameter( Mandatory, ValueFromPipeline )]
[string] $InputObject
)
begin {
Write-Host '[begin]'
$steppablePipeline = {
Out-File -LiteralPath $LOG_FILE_PATH
}.GetSteppablePipeline( $MyInvocation.CommandOrigin )
$steppablePipeline.Begin( $true )
}
process {
Write-Host '[process]'
$steppablePipeline.Process( $InputObject )
$InputObject # Forward to next command in pipeline of caller
}
end {
Write-Host '[end]'
$steppablePipeline.End()
}
}
try {
'foo', 'bar' |
ForEach-Object { throw 'my error' } |
Test-SteppablePipeline
}
catch {
'[catch]'
}
Write-Host '[add]'
Add-Content -LiteralPath $LOG_FILE_PATH -Value 'baz'
输出:
[begin]
[process]
[catch]
[add]
Add-Content: C:\Test.ps1:48
Line |
48 | Add-Content -LiteralPath $LOG_FILE_PATH -Value 'baz'
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| The process cannot access the file 'C:\LogFile.log' because it is being used by another process.
正如您通过[messages]
所看到的,由于异常,Test-SteppablePipeline
的end
block 不会被调用,这可能是文件未关闭的原因。
最佳答案
值得注意的一点:这不仅仅是可步进管道的问题,而是任何获取 handle 的 PowerShell 函数的问题。并且需要确保在退出之前正确处理该句柄。
这是一个大问题,在 PowerShell 7.3 中通过 clean
block 解决了 zett42 points out in his helpful answer 。对于任何想知道 PowerShell 7.2 及之前版本中如何解决此问题的人,答案是,仅使用 PowerShell 无法解决,解决方案是创建一个实现 IDisposable
interface 的二进制 cmdlet。 ,引擎将完成其余的工作(确保不变地调用 .Dispose()
,包括 CTRL + C 或终止错误)。
使用二进制 cmdlet 的 zett42 代码的最小重现。请注意,为了演示,这是使用 Add-Type
和内联 C# 在运行时进行编译,理想情况下,该 cmdlet 会被预编译。
Add-Type '
using System;
using System.Management.Automation;
namespace Test
{
[Cmdlet(VerbsDiagnostic.Test, "SteppablePipeline")]
public sealed class TestSteppablePipelineCommand : PSCmdlet, IDisposable
{
private SteppablePipeline _pipe;
[Parameter(Mandatory = true, ValueFromPipeline = true)]
public string InputObject { get; set; }
protected override void BeginProcessing()
{
WriteInformation("[begin]", null);
_pipe = ScriptBlock.Create("Out-File -LiteralPath $LOG_FILE_PATH")
.GetSteppablePipeline(MyInvocation.CommandOrigin);
_pipe.Begin(true);
}
protected override void ProcessRecord()
{
WriteInformation("[process]", null);
_pipe.Process(InputObject);
WriteObject(InputObject, enumerateCollection: true);
}
protected override void EndProcessing()
{
WriteInformation("[end]", null);
_pipe.End();
}
public void Dispose()
{
_pipe.Dispose();
}
}
}' -PassThru -IgnoreWarnings -WA 0 | Import-Module -Assembly { $_.Assembly }
$LOG_FILE_PATH = Join-Path $pwd.Path LogFile.log
try {
0..10 |
Test-SteppablePipeline -InformationAction Continue |
ForEach-Object {
if ($_ -eq 5) {
throw 'my error'
}
}
}
catch {
'[catch]'
}
Write-Host '[add]'
Add-Content -LiteralPath $LOG_FILE_PATH -Value 'baz'
Get-Content $LOG_FILE_PATH
关于powershell - 即使发生异常,如何确保可步进管道使用的文件也已关闭?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77282823/