powershell - 即使发生异常,如何确保可步进管道使用的文件也已关闭?

标签 powershell pipeline powershell-core powershell-7.3

这是一个 self 回答问题。我将仅添加针对 PowerShell 7.3 或更高版本的答案。请随意添加任何以前的 PowerShell 版本的答案。


我有一个自定义日志记录函数,它接受管道输入并使用可步进管道对象将其输入正确转发到Out-FileThis 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-SteppablePipelineend 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/

相关文章:

arrays - 为什么$ A + $ B和$ A,$ B与测试路径交互不同

python - 使用 spark ml(数据框)进行逻辑回归

powershell - powershell、pwsh 和 powershell_ise 之间奇怪的[类型]行为差异

powershell - 如何在 ForEach-Object -Parallel 中传递自定义函数

powershell - 为什么 `Install-Module` 不遵守脚本范围内的 $ErrorActionPreference=Stop ?

file - Powershell输出到文件和控制台

.net - 是否存在指定的(子)索引分隔符?

powershell - 通过PowerShell从名称运行程序(类似于运行框)

azure - 使用 powershell 获取 Azure DevOps 服务连接服务主体 ID

python - Scikit 学习管道类型错误 : zip argument #2 must support iteration