我被要求修复同事今天写的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
循环开始而不是在可迭代对象中进行管道时,为什么管道工作失败?我自己很少使用第一种格式,所以我没有用我编写的代码遇到这个问题。我无法想到两种格式都不起作用的功能性原因,因为如果管道输入为空,则会出现一个适当的异常,在这种情况下会抛出。
答案 0 :(得分:1)
一个是语言关键字foreach
,另一个是cmdlet ForEach-Object
的别名。
语言关键字不能成为管道的一部分,这就是您获得该异常的原因。这也是为什么它们在不同的上下文中表示不同的东西,如果它已经是管道的一部分,引擎将不会将foreach
解析为关键字。
答案 1 :(得分:1)
当语句以
foreach
循环开始而不是在可迭代对象中进行管道时,为什么管道不能工作?
因为一个语法是foreach
语句,另一个是ForEach-Object
命令的别名。这就像Get-ChildItem
和if {} 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
命令始终使用(并要求)管道。