关于 Pipelines

简短说明

将命令在 PowerShell 中合并为管道

详细说明

管道是由管道操作员(|) (ASCII 124) 连接的一系列命令。 每个管道运算符将上述命令的结果发送到下一个命令。

可以将第一个命令的输出作为输入发送到第二个命令进行处理。 该输出可以发送到另一个命令。 结果是一个复杂的命令链或 管道 ,它由一系列简单的命令组成。

例如,

Command-1 | Command-2 | Command-3

在此示例中,Command-1 发出的对象将发送到 Command-2Command-2 处理对象并将其发送到 Command-3Command-3 处理对象并将其发送到管道。 由于管道中没有更多命令,因此结果将显示在控制台中。

在管道中,命令按从左到右的顺序进行处理。 处理将作为单个操作进行处理,并且输出在生成时显示。

下面是一个简单的示例。 以下命令获取记事本进程,然后停止它。

例如,

Get-Process notepad | Stop-Process

第一个命令使用 Get-Process cmdlet 获取表示记事本进程的对象。 它使用管道运算符(|)将进程对象发送到停止记事本进程的 Stop-Process cmdlet。 请注意,Stop-Process 命令没有 名称ID 参数来指定进程,因为指定的进程是通过管道提交的。

此管道示例获取当前目录中的文本文件,仅选择长度超过 10,000 字节的文件,按长度对这些文件进行排序,并在表中显示每个文件的名称和长度。

Get-ChildItem -Path *.txt |
  Where-Object {$_.length -gt 10000} |
    Sort-Object -Property length |
      Format-Table -Property name, length

此管道按指定顺序包含四个命令。 下图展示了每个命令的输出如何传递到管道中的下一个命令。

Get-ChildItem -Path *.txt
| (FileInfo objects for *.txt)
V
Where-Object {$_.length -gt 10000}
| (FileInfo objects for *.txt)
| (      Length > 10000      )
V
Sort-Object -Property Length
| (FileInfo objects for *.txt)
| (      Length > 10000      )
| (     Sorted by length     )
V
Format-Table -Property name, length
| (FileInfo objects for *.txt)
| (      Length > 10000      )
| (     Sorted by length     )
| (   Formatted in a table   )
V

Name                       Length
----                       ------
tmp1.txt                    82920
tmp2.txt                   114000
tmp3.txt                   114000

使用管道

大多数 PowerShell cmdlet 旨在支持管道。 在大多数情况下,可以将Get cmdlet 的结果通过管道传递给同一名词的另一个 cmdlet。 例如,可以通过管道将 Get-Service cmdlet 的输出传递给 Start-ServiceStop-Service cmdlet。

此示例管道在计算机上启动 WMI 服务:

Get-Service wmi | Start-Service

例如,可以将 PowerShell 注册表提供程序中 Get-ItemGet-ChildItem 的输出传递给 New-ItemProperty cmdlet。 此示例向 MyCompany 注册表键中添加了一个新的注册表项 NoOfEmployees,其值为 8124

Get-Item -Path HKLM:\Software\MyCompany |
  New-ItemProperty -Name NoOfEmployees -Value 8124

许多实用工具 cmdlet(例如 Get-MemberWhere-ObjectSort-ObjectGroup-ObjectMeasure-Object)几乎完全用于管道中。 可以通过管道将任何对象类型传递给这些 cmdlet。 此示例演示如何按每个进程中打开的句柄数对计算机上的所有进程进行排序。

Get-Process | Sort-Object -Property handles

可以通过管道将对象传递给格式化、导出和输出 cmdlet,例如 Format-ListFormat-TableExport-ClixmlExport-CSVOut-File

此示例演示如何使用 Format-List cmdlet 显示进程对象的属性列表。

Get-Process winlogon | Format-List -Property *

通过一些实践,你会发现将简单的命令合并到管道中可以节省时间和键入,并使脚本更高效。

管道的工作原理

本部分介绍如何将输入对象绑定到 cmdlet 参数,并在管道执行期间进行处理。

接受管道输入

若要支持管道传送,接收 cmdlet 必须具有接受管道输入的参数。 将 Get-Help 命令与 FullParameter 选项一起使用,以确定 cmdlet 接受管道输入的参数。

例如,若要确定 Start-Service cmdlet 的哪些参数接受管道输入,请键入:

Get-Help Start-Service -Full

Get-Help Start-Service -Parameter *

Start-Service cmdlet 的帮助显示,只有 InputObjectName 参数接受管道输入。

-InputObject <ServiceController[]>
Specifies ServiceController objects representing the services to be started.
Enter a variable that contains the objects, or type a command or expression
that gets the objects.

Required?                    true
Position?                    0
Default value                None
Accept pipeline input?       True (ByValue)
Accept wildcard characters?  false

-Name <String[]>
Specifies the service names for the service to be started.

The parameter name is optional. You can use Name or its alias, ServiceName,
or you can omit the parameter name.

Required?                    true
Position?                    0
Default value                None
Accept pipeline input?       True (ByPropertyName, ByValue)
Accept wildcard characters?  false

将对象通过管道发送到 Start-Service时,PowerShell 会尝试将对象与 InputObjectName 参数相关联。

接受管道输入的方法

Cmdlet 参数可以通过以下两种不同的方式之一接受管道输入:

  • ByValue:参数接受与预期的 .NET 类型匹配的值或可转换为该类型的值。

    例如,Start-Service参数接受按值输入的管道。 它可以接受可转换为字符串的字符串对象或对象。

  • ByPropertyName:仅当输入对象具有与参数同名的属性时,参数才接受输入。

    例如,Start-Service 的 Name 参数可以接受具有 Name 属性的对象。 若要列出对象的属性,请通过管道将其传递给 Get-Member

某些参数可以通过值或属性名称接受对象,以便更轻松地从管道获取输入。

参数绑定

将对象从一个命令传递给另一个命令时,PowerShell 会尝试将管道对象与接收 cmdlet 的参数相关联。

PowerShell 的参数绑定组件根据以下条件将输入对象与 cmdlet 参数相关联:

  • 参数必须接受来自管道的输入。
  • 该参数必须接受要发送的对象的类型或可转换为预期类型的类型。
  • 该参数未在命令中使用。

例如,Start-Service cmdlet 具有许多参数,但其中只有两个参数,NameInputObject 接受管道输入。 Name 参数采用字符串,InputObject 参数采用服务对象。 因此,你可以通过管道传递字符串、服务对象,以及具有可以转换为字符串或服务对象的属性的对象。

PowerShell 尽可能高效地管理参数绑定。 无法建议或强制 PowerShell 绑定到特定参数。 如果 PowerShell 无法绑定管道对象,该命令将失败。

有关排查绑定错误的详细信息,请参阅本文后面的调查管道错误

一次性处理

将对象管道传递给命令非常类似于使用命令的参数来提交对象。 让我们看看一个管道示例。 在此示例中,我们使用管道来显示服务对象的表。

Get-Service | Format-Table -Property Name, DependentServices

在功能上,这类似于使用 Format-Table 参数来提交对象集合。

例如,我们可以将服务集合保存到使用 InputObject 参数传递的变量中。

$services = Get-Service
Format-Table -InputObject $services -Property Name, DependentServices

或者,我们可以在 InputObject 参数中嵌入命令。

Format-Table -InputObject (Get-Service) -Property Name, DependentServices

但是,有一个重要区别。 将多个对象通过管道传递给命令时,PowerShell 一次将对象发送到一个命令。 使用命令参数时,对象将作为单个数组对象发送。 这种细微差异具有重大后果。

执行管道时,PowerShell 会自动枚举实现接口的 IEnumerable 任何类型的成员,并通过管道一次发送一个成员。 例外情况是 [hashtable],它需要调用 GetEnumerator() 该方法。

在以下示例中,数组和哈希表通过管道传递给 Measure-Object cmdlet,以计算从管道接收的对象数。 该数组具有多个成员,哈希表具有多个键值对。 一次只枚举一个数组。

@(1,2,3) | Measure-Object
Count    : 3
Average  :
Sum      :
Maximum  :
Minimum  :
Property :
@{"One"=1;"Two"=2} | Measure-Object
Count    : 1
Average  :
Sum      :
Maximum  :
Minimum  :
Property :

同样,如果将多个进程对象从 Get-Process cmdlet 传递给 Get-Member cmdlet,PowerShell 将每个进程对象逐个发送到 Get-MemberGet-Member 显示进程对象的 .NET 类(类型),以及它们的属性和方法。

Get-Process | Get-Member
TypeName: System.Diagnostics.Process

Name      MemberType     Definition
----      ----------     ----------
Handles   AliasProperty  Handles = Handlecount
Name      AliasProperty  Name = ProcessName
NPM       AliasProperty  NPM = NonpagedSystemMemorySize
...

注释

Get-Member 消除重复项,因此,如果对象都是同一类型,则它只显示一种对象类型。

但是,如果使用 Get-Member 参数,则 Get-Member 接收 System.Diagnostics.Process 对象的数组作为单个单元。 它显示对象的数组的属性。 (请注意 [] 类型名称之后的数组符号()。

例如,

Get-Member -InputObject (Get-Process)
TypeName: System.Object[]

Name               MemberType    Definition
----               ----------    ----------
Count              AliasProperty Count = Length
Address            Method        System.Object& Address(Int32 )
Clone              Method        System.Object Clone()
...

此结果可能不是预期的结果。 但了解后,可以使用它。 例如,所有数组对象都有一个 Count 属性。 可以使用该函数对计算机上运行的进程数进行计数。

例如,

(Get-Process).count

记住,通过管道传递的对象是一个接一个地进行传递的。

调查管道错误

当 PowerShell 无法将管道对象与接收 cmdlet 的参数相关联时,命令将失败。

在以下示例中,我们尝试将注册表项从一个注册表项移动到另一个注册表项。 Get-Item cmdlet 获取目标路径,然后通过管道传递给 Move-ItemProperty cmdlet。 Move-ItemProperty 命令指定要移动的注册表项的当前路径和名称。

Get-Item -Path HKLM:\Software\MyCompany\sales |
Move-ItemProperty -Path HKLM:\Software\MyCompany\design -Name product

该命令失败,PowerShell 会显示以下错误消息:

Move-ItemProperty : The input object can't be bound to any parameters for
the command either because the command doesn't take pipeline input or the
input and its properties do not match any of the parameters that take
pipeline input.
At line:1 char:23
+ $a | Move-ItemProperty <<<<  -Path HKLM:\Software\MyCompany\design -Name p

若要调查,请使用 Trace-Command cmdlet 跟踪 PowerShell 的参数绑定组件。 以下示例在执行管道时跟踪参数绑定。 PSHost 参数在控制台中显示跟踪结果,FilePath 参数将跟踪结果发送到 debug.txt 文件以供以后参考。

Trace-Command -Name ParameterBinding -PSHost -FilePath debug.txt -Expression {
  Get-Item -Path HKLM:\Software\MyCompany\sales |
    Move-ItemProperty -Path HKLM:\Software\MyCompany\design -Name product
}

跟踪的结果很长,但它们显示绑定到 Get-Item cmdlet 的值,然后显示绑定到 Move-ItemProperty cmdlet 的命名值。

...
BIND NAMED cmd line args [`Move-ItemProperty`]
BIND arg [HKLM:\Software\MyCompany\design] to parameter [Path]
...
BIND arg [product] to parameter [Name]
...
BIND POSITIONAL cmd line args [`Move-ItemProperty`]
...

最后,它显示尝试将路径与 目标的 参数 Move-ItemProperty 绑定失败。

...
BIND PIPELINE object to parameters: [`Move-ItemProperty`]
PIPELINE object TYPE = [Microsoft.Win32.RegistryKey]
RESTORING pipeline parameter's original values
Parameter [Destination] PIPELINE INPUT ValueFromPipelineByPropertyName NO
COERCION
Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName NO
COERCION
...

使用 Get-Help cmdlet 查看 Destination 参数的属性。

Get-Help Move-ItemProperty -Parameter Destination

-Destination <String>
    Specifies the path to the destination location.

    Required?                    true
    Position?                    1
    Default value                None
    Accept pipeline input?       True (ByPropertyName)
    Accept wildcard characters?  false

结果显示,目标仅“按属性名称”接受管道输入。 因此,管道对象必须具有名为 Destination的属性。

使用 Get-Member 查看来自 Get-Item的对象的属性。

Get-Item -Path HKLM:\Software\MyCompany\sales | Get-Member

输出显示该项是 Microsoft.Win32.RegistryKey 对象,该对象没有 Destination 属性。 这解释了命令失败的原因。

路径参数按名称或值接受管道输入。

Get-Help Move-ItemProperty -Parameter Path

-Path <String[]>
    Specifies the path to the current location of the property. Wildcard
    characters are permitted.

    Required?                    true
    Position?                    0
    Default value                None
    Accept pipeline input?       True (ByPropertyName, ByValue)
    Accept wildcard characters?  true

若要修复命令,必须在 Move-ItemProperty cmdlet 中指定目标,并使用 Get-Item 获取要移动的项 路径

例如,

Get-Item -Path HKLM:\Software\MyCompany\design |
Move-ItemProperty -Destination HKLM:\Software\MyCompany\sales -Name product

内部行续行符

如前所述,管道是由管道操作员(|)连接的一系列命令,通常用单行编写。 但是,为了便于阅读,PowerShell 允许跨多行拆分管道。 当管道运算符是行上的最后一个标记时,PowerShell 分析器会将下一行联接到当前命令,以继续构造管道。

例如,以下单行管道:

Command-1 | Command-2 | Command-3

可以编写为:

Command-1 |
  Command-2 |
    Command-3

后续行的前导空格不重要。 缩进增强了可读性。

另请参阅

about_PSReadLine

about_Objects

关于_参数

关于_命令_语法

about_ForEach