PowerShell 管道是 PowerShell shell 和脚本语言中最重要(也是最有用)的功能之一。一旦您了解了它的基本工作原理和能力,就可以利用它的强大功能来编写自己的函数。在本教程中,您将正是这样做!
PowerShell 管道允许您将命令链接在一起,构建一个单一的“管道”,这简化了代码,允许并行处理等等。如果您准备好了解管道并构建自己的函数以利用管道,请开始吧!
先决条件
本文将是一个教程,全部都是实际演示。如果您想跟着做,您需要 PowerShell v3+。本教程将使用 Windows PowerShell v5.1。
理解 PowerShell 管道
大多数 PowerShell 命令通过参数接收一些输入。命令通过参数接收一些对象作为输入,然后在内部对其进行操作。然后,它可以选择通过输出返回一些对象。
管道中的命令就像接力赛中的人类接力选手一样。接力赛中的每个选手(对象)都接收前一个选手的接力棒,并将其传递给下一个选手,除了第一个和最后一个选手。
例如,Stop-Service
命令有一个名为 InputObject
的参数。此参数允许您向 Stop-Service
传递表示您想要停止的 Windows 服务的特定类型的对象。
要使用 InputObject
参数,您可以通过 Get-Service
检索服务对象,然后将对象传递给 InputObject
参数,如下所示。通过 InputObject
参数提供输入给 Stop-Service
命令的这种方法非常有效,可以完成任务。
通过 InputObject
参数传递输入给 Stop-Service
命令的这种方法需要两个不同的步骤。PowerShell 必须首先运行 Get-Service
,将输出保存到变量中,然后通过 InputObject
参数将该值传递给 Stop-Service
。
现在,将上述片段与下面的片段进行对比,它们完成相同的任务。下面的片段要简单得多,因为您不必创建 $services
变量,甚至根本不使用 InputObject
参数。相反,PowerShell “知道”您打算使用 InputObject
参数。它通过一种称为参数绑定的概念实现了这一点。
您现在使用了 |
运算符将命令链接在一起。您创建了一个管道。
但是,您不必仅使用两个命令创建管道;如果命令参数支持,您可以将尽可能多的命令链接在一起。例如,下面的代码片段:
- 将
Get-Service
cmdlet 返回的所有对象传递给Where-Object
cmdlet。 Where-Object
cmdlet 然后查看每个对象的Status
属性,然后仅返回具有值Running
的对象。- 然后,将这些对象中的每一个发送到
Select-Object
,它仅返回对象的Name
和DisplayName
属性。 - 由于没有其他 cmdlet 接受
Select-Object
输出的对象,因此命令直接将对象返回到控制台。
Where-Object
和Select-Object
cmdlet 了解如何通过一个叫做参数绑定的概念来处理管道输入,下一节将讨论这个概念。
有关管道的附加信息,请运行命令
Get-Help about_pipelines
。
管道参数绑定
乍一看,管道可能看起来微不足道。毕竟,它只是从一个命令传递对象到另一个命令。但实际上,管道要复杂得多。命令只通过参数接受输入。即使您没有明确定义它,管道也必须想办法确定使用哪个参数。
确定命令通过管道接收输入时使用哪个参数的任务被称为参数绑定。要成功地将来自管道的对象绑定到参数,传入命令的参数必须支持它。命令参数以两种方式之一支持管道参数绑定:ByValue
和/或ByPropertyName
。
按值
命令参数接受整个传入对象作为参数值。ByValue
参数在传入对象中查找特定类型的对象。如果该对象类型匹配,PowerShell假定该对象是要绑定到该参数的,并接受它。
Get-ChildItem
cmdlet 有一个名为Path
的参数,它接受字符串对象类型和通过ByValue
的管道输入。因此,运行类似'C:\Windows' | Get-ChildItem
的命令会返回 C:\Windows 目录中的所有文件,因为C:\Windows
是一个字符串。
按属性名
命令参数不接受整个对象,而是接受该对象的单个属性。它这样做不是通过查看对象类型,而是通过查看属性名称。
Get-Process
命令有一个Name
参数,该参数设置为接受管道输入ByPropertyName
。当你将具有Name
属性的对象传递给Get-Process
命令时,比如[pscustomobject]@{Name='firefox'} | Get-Process
,PowerShell 会将传入对象上的Name
属性与Name
参数进行匹配或绑定,并使用该值。
发现支持管道的命令参数
如前所述,并非每个命令都支持管道输入。命令作者必须在开发中创建该功能。命令必须至少有一个支持管道的参数,放置 ByValue
或 ByPropertyName
。
你如何知道哪些命令及其参数支持管道输入呢?你可以通过试错来尝试,但使用 PowerShell 帮助系统的 Get-Help
命令有更好的方法。
例如,看看下面的 Get-ChildItem
命令的 Path
参数。你可以看到它支持两种类型的管道输入。

一旦您知道哪些命令参数支持管道输入,您就可以利用该功能,如下所示。
但另一方面,Get-Service
上的 DisplayName
参数不支持管道输入。

构建您自己的管道函数
尽管标准的 PowerShell cmdlet 支持管道输入,但这并不意味着您不能利用这个功能。幸运的是,您可以构建接受管道输入的函数。
为了演示,让我们从一个名为 Get-ConnectionStatus
的现有函数开始。
- 该函数有一个单一参数(不接受管道输入),名为
ComputerName
,允许您将一个或多个字符串传递给它。您可以通过它没有定义为参数属性([Parameter()]
)来判断ComputerName
参数不接受管道输入。 - 然后,该函数读取这些字符串并对每个字符串运行
Test-Connection
cmdlet。 - 对于传递的每个字符串计算机名称,然后返回一个具有
ComputerName
和Status
属性的对象。
然后,通过传递 ComputerName
参数调用 Get-ConnectionStatus
,传递一个或多个主机名或 IP 地址,如下所示。
步骤 1:允许管道输入
要使此函数接受管道输入,必须首先定义适当的参数属性。该属性可以是 ValueFromPipeline
以接受通过值的管道输入,或者是 ValueFromPipelineByPropertyName
以接受通过属性名的管道输入。
在本例中,将 ValueFromPipeline
参数属性添加到 [Parameter()]
定义的括号内,如下所示。
此时,这就是你需要做的一切。 Get-ConnectionStatus
函数现在将任何传递给它的字符串对象绑定到 ComputerName
参数。但是,即使进行了参数绑定,也不意味着函数会对其执行有意义的操作。
步骤 2:添加一个处理块
当你希望 PowerShell 处理从管道中传入的所有对象时,必须接下来添加一个 Process
块。此块告诉 PowerShell 对从管道中传入的每个对象进行处理。
如果没有
Process
块,PowerShell 将仅处理来自管道的第一个对象。Process
块告诉 PowerShell 继续处理对象。
添加一个Process
块,如下所示,通过将函数的所有功能包含在其中来完成。
始终从
Process
块内发送输出。从Process
块内发送输出会将对象发送到管道,使其他命令可以从管道接受这些对象。
将对象传递到PowerShell管道
一旦在上面的函数中定义了Process
块,您现在可以通过管道传递值到ComputerName
参数来调用该函数,如下所示。
在这一点上,您可以利用管道的真正力量,并开始将更多命令合并到其中。例如,也许您有一个文本文件,C:\Test\computers.txt,其中有一行IP地址,通过换行符分隔,如下所示。
然后,您可以使用Get-Content
命令读取文本文件中的每个IP地址,并将它们直接传递给Get-ConnectionStatus
函数。
将此设置进一步,您可以直接将Get-ConnectionStatus
返回的对象传递到ForEach-Object
命令中。
以下是代码:
- 读取文本文件中的所有计算机名称,并将它们传递给
Get-ConnectionStatus
函数。 Get-ConnectionStatus
处理每个计算机名称,并返回具有属性ComputerName
和Status
的对象。Get-ConnectionStatus
然后将每个对象传递给ForEach-Object
命令,该命令将以青色显示的单个字符串返回一个可读的状态。
如果未在
ComputerName
参数上启用管道输入,或者Get-ConnectionStatus
在Process
块中未返回对象,则PowerShell在处理所有对象(IP地址)之前不会向控制台返回任何状态。
按属性名称进行管道绑定
到目前为止,Get-ConnectionStatus
命令已设置为接受ByValue(ValueFromPipeline
)的管道输入,方法是接受类似'127.0.0.1','192.168.1.100'
的字符串数组。如果输入是从CSV文件而不是IP地址的文本文件接收到的,该函数是否也能正常工作?
也许您有一个CSV文件,位于C:\Test\pc-list.csv如下所示。
请注意,CSV文件中的
ComputerName
字段与Get-ConnnectionStatus
的ComputerName
参数名称相同。
如果您尝试导入CSV并将其通过管道传递给Get-ConnectionStatus
,则该函数会在ComputerName
列中返回意外结果。
你能猜到出了什么问题吗?毕竟,参数名称确实匹配,那么为什么 PowerShell 管道没有将 Import-CSV
返回的输出绑定到 Get-ConnectionStatus
上的 ComputerName
参数?因为你需要参数属性 ValueFromPipelineByPropertyName
。
就目前而言,函数的 ComputerName
参数具有以下参数定义:[Parameter(ValueFromPipeline)]
。因此,你必须添加 ValueFromPipelineByPropertyName
以将 ComputerName
参数设置为支持按属性名称输入,如下所示。
一旦你启用了 ByPropertyName 的管道支持,就告诉 PowerShell 开始查看对象属性名称和对象类型。一旦你做出了这个改变,你应该能够看到预期的输出。
摘要
在本教程中,你学会了 PowerShell 管道的工作原理,它如何绑定参数,甚至如何创建支持 PowerShell 管道的自定义函数。
尽管函数可以在没有管道的情况下工作,但它们无法将对象从一个命令流式传输到另一个命令并简化代码。
你能想到一个你写过或即将写的函数,可以受益于使其支持管道吗?