"不允许空管"在foreach循环中

时间:2017-08-24 20:22:25

标签: loops powershell foreach

我被要求修复同事今天写的PowerShell脚本的输出,并在尝试从foreach循环管道输出时发现了一些奇怪的行为。当我运行循环而没有将可迭代对象$gpos发送到foreach时,如下所示:

# This is the foreach loop in question
foreach ( $gpo in $gpos ) {
  [xml]$XML = Get-GPOReport -$gpo.DisplayName -ReportType Xml
  $admins = $XML.DocumentElement.Computer.ExtensionData.Extention.RestrictedGroups.Member

  # Not this one 
  $admins | foreach {
    [PSCustomObject]@{
      "GroupPolicy" = $gpo.DisplayName;
      "Permisisons" = $_.name.'#text';
    }
  }
} | Export-CSV -Path \path\to\file.csv -NoTypeInformation

我收到错误"不允许空管道元素"。

但是,如果我将$gpos对象传递到foreach循环中,如下所示:

$gpos | foreach {
  $gpo = $_
  # ...the rest of the code above
} | Export-CSV -Path \path\to\file.csv -NoTypeInformation

我可以毫无问题地使用最后一个管道。当语句以foreach循环开始而不是在可迭代对象中进行管道时,为什么管道工作失败?我自己很少使用第一种格式,所以我没有用我编写的代码遇到这个问题。我无法想到两种格式都不起作用的功能性原因,因为如果管道输入为空,则会出现一个适当的异常,在这种情况下会抛出。

2 个答案:

答案 0 :(得分:1)

一个是语言关键字foreach,另一个是cmdlet ForEach-Object的别名。

语言关键字不能成为管道的一部分,这就是您获得该异常的原因。这也是为什么它们在不同的上下文中表示不同的东西,如果它已经是管道的一部分,引擎将不会将foreach解析为关键字。

答案 1 :(得分:1)

  

当语句以foreach循环开始而不是在可迭代对象中进行管道时,为什么管道不能工作?

因为一个语法是foreach语句,另一个是ForEach-Object命令的别名。这就像Get-ChildItemif {} else {}之间的区别。

PowerShell作者愚蠢地认为重载这个词是个好主意。从那时起,它就混淆了语言的用户。

比较

Get-Help about_Foreach -ShowWindow

Get-Help ForEach-Object -ShowWindow

前者甚至描述了PowerShell如何决定哪个:

  

当Foreach出现在命令管道中时,Windows PowerShell使用foreach别名,该别名调用ForEach-Object命令。在命令管道中使用foreach别名时,不要像使用Foreach语句那样包含($ in $)语法。这是因为管道中的先前命令提供了此信息。

底线是foreach不会向管道发送输出。你可以做得很好:

$x = foreach ($i in 1..10) { $i }

但这会失败:

foreach ($i in 1..10) { $i } | Where-Object { $_ -eq 2 }

正如Mathias R. Jessen在评论中指出的那样,您可以将foreach语句包装在子表达式中,以使其与管道一起使用:

$(foreach ($i in 1..10) { $i }) | Where-Object { $_ -eq 2 }

ForEach-Object命令始终使用(并要求)管道。