为什么 Write-Output 在 PowerShell class 方法中不起作用?

Why does Write-Output not work inside a PowerShell class method?

我正在尝试使用 Write-Output 输出变量,但它在 PowerShell class 方法中不起作用。 Write-Host 正在工作。请参阅下面的示例代码。

class sample {
  [string] sampleMethod() {
    $output = "Output message"
    try {
      Write-Output $output
      throw "error"
    }
    catch {
      Write-Output $output
      $_
    }
    return "err"
  }
}    

$obj = [sample]::new()
$obj.sampleMethod()

Write-Output 在 class 方法中不起作用有什么具体原因吗?

来自docs

In class methods, no objects get sent to the pipeline except those mentioned in the return statement. There's no accidental output to the pipeline from the code.

This is fundamentally different from how PowerShell functions handle output, where everything goes to the pipeline.

如果你只是为了调试或其他什么需要输出,你可以使用 Write-HostWrite-Warning 等,它们基本上只是写入控制台。

添加到

将方法签名 ([string] sampleMethod()) 视为 合同 - 您向用户承诺,如果他们使用 0 个参数调用该方法,它将始终 return 正好一个 [string] 对象

在方法执行期间允许任意数量的 Write-Output 语句将违反该合同

虽然写输出在 class' 方法中不起作用,但它 确实 如果方法 returns 是一个脚本块在外面执行,像这样:

#Cmdlet you can't edit that outputs whilst running
function foo {
    write-output "Beginning complex operation!";
    start-sleep 2;
    write-output "Important information you would rather not have to wait for!";
    start-sleep 2;
    write-output "Operation finished!";
}

class IsClass{
    static [ScriptBlock]bar(){
        #create a ScriptBlock that the must be executed outside
        return { foo };
    }
}

& $([IsClass]::bar());
<#Output:
Beginning complex operation!
[two second wait]
Important information you would rather not have to wait for!
[two second wait]
Operation finished!
#>

这是一个相对 hacky 的解决方案。不过,据我所知,当 cmdlet 仍为 运行 时,这是编写在静态方法内部调用的 cmdlet 输出的唯一方法。如果您无权访问在 class.

中调用的 cmdlet,则不能在方法调用的 cmdlet 中使用 write-host

不使用脚本块的示例:

#Cmdlet you can't edit that outputs whilst running
function foo {
    write-output "Beginning complex operation!";
    start-sleep 2;
    write-output "Important information you would rather not have to wait for!";
    start-sleep 2;
    write-output "Operation finished!";
}

#Class that uses the mentioned cmdlet
class IsClass{
    static [void]bar(){
        #Directly invoke the method
        write-host $(foo);
    }
}

[IsClass]::bar();
<#Output:
[Awkward 4 second pause]
Beginning complex operation! Important information you would rather not have to wait for! Operation finished!

还值得注意的是,第二种方法会导致所有输出显示在一行上。

您可能希望实际使用它的一个场景是,如果您正在编写一个脚本来使用命令行安装工具。安装使用您无法控制的 cmdlet,需要几分钟才能完成(例如使用 chocolatey 安装软件)。这意味着如果 cmdlet 的进度发生变化(例如移动到安装软件的依赖项),它无法将更改写入控制台,直到完整安装完成,让用户对当前发生的事情一无所知。

更新: 在写这篇文章时,我还遇到了很多关于脚本块内作用域使用的问题,因为它们不共享创建它们的上下文的范围,只共享它们执行的范围。这在很大程度上使我在这里提到的很多内容无效,因为这意味着您无法引用 class.

的属性

更新 2: 除非你使用 GetNewClosure!

    static [ScriptBlock]bar(){
        #create a ScriptBlock that the must be executed outside
        $that = $this;
        return { $that.ClassVariable }.GetNewClosure();
    }