当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 了解 PowerShell 管道和创建函数

作者:精品下载站 日期:2024-12-14 13:07:48 浏览:13 分类:玩电脑

了解 PowerShell 管道和创建函数


PowerShell 管道是 PowerShell shell 和脚本语言最重要(也是最有用)的功能之一。一旦您了解了它的工作原理和功能的基础知识,您就可以在自己的功能中利用它的强大功能。在本教程中,您将这样做!

PowerShell 管道允许您将命令链接在一起以构建单个“管道”,从而简化代码、允许并行处理等。如果您准备好了解管道并构建自己的函数来利用管道,那么让我们开始吧!

先决条件

这篇文章将是一个教程,并且将是所有实践演示。如果您想继续操作,您将需要 PowerShell v3+。本教程将使用 Windows PowerShell v5.1。

了解 PowerShell 管道

大多数 PowerShell 命令通过参数接收一些输入。该命令接收一些对象作为输入,并在内部对其执行某些操作。然后它可以选择通过输出返回一些对象。

管道中的命令就像接力赛中的人类赛跑者一样。比赛中的每个参赛者,除了第一名和最后一名之外,都从前一位选手手中接过接力棒(物体),并将其传递给下一位选手。

例如,Stop-Service cmdlet 有一个名为 InputObject 的参数。此参数允许您将特定类型的对象传递给 Stop-Service 来表示您想要停止的 Windows 服务。

要使用 InputObject 参数,您可以通过 Get-Service 检索服务对象,然后将该对象传递给 InputObject 参数,如下所示。这种通过 InputObject 参数向 Stop-Service cmdlet 提供输入的方法效果很好,可以完成工作。

$service = Get-Service -Name 'wuauserv'
Stop-Service -InputObject $service

这种将输入传递给 Stop-Service 命令的方法需要两个不同的步骤。 PowerShell 必须首先运行 Get-Service,将输出保存到变量,然后通过 InputObject 参数将该值传递给 Stop-Service

现在,将上面的代码片段与下面的代码片段进行收缩,它的作用是相同的。它要简单得多,因为您根本不需要创建 $services 变量,甚至不需要使用 InputObject 参数。相反,PowerShell“知道”您打算使用 InputObject 参数。它通过称为参数绑定的概念来实现这一点。

您现在已经将命令与 | 运算符“链接”在一起。您已经创建了一个管道

Get-Service -Name 'wuauserv' | Stop-Service

但是,您不必仅使用两个命令来创建管道;您可以将任意数量的链接在一起(如果命令参数支持)。例如,下面的代码片段:

  1. Get-Service cmdlet 返回的所有对象传递给 Where-Object cmdlet。
  2. 然后,Where-Object cmdlet 查看每个对象的 Status 属性,然后仅返回那些值为 Running 的对象。
  3. 然后,每个对象都会发送到 Select-Object,后者仅返回对象的 NameDisplayName 属性。
  4. 由于没有其他 cmdlet 接受 Select-Object 输出的对象,因此该命令将对象直接返回到控制台。

Where-ObjectSelect-Object cmdlet 了解如何通过称为参数绑定的概念处理管道输入,下一节将对此进行讨论。

Get-Service | Where-Object Status -eq Running | Select-Object Name, DisplayName

有关管道的更多信息,请运行命令Get-Help about_pipelines

管道参数绑定

乍一看,管道可能看起来微不足道。毕竟,它只是将对象从一个命令传递到另一个命令。但实际上,管道要复杂得多。命令仅接受通过参数的输入。即使您没有显式定义参数,管道也必须以某种方式确定要使用哪个参数。

当命令通过管道接收输入时确定要使用哪个参数的任务称为参数绑定。为了成功将从管道传入的对象绑定到参数,传入命令的参数必须支持它。命令参数支持以两种方式之一进行管道参数绑定; ByValue 和/或 ByPropertyName

按价值

命令参数接受整个传入对象作为参数值。 ByValue 参数在传入对象中查找特定类型的对象。如果该对象类型匹配,PowerShell 会假定该对象应绑定到该参数并接受它。

Get-ChildItem cmdlet 有一个名为 Path 的参数,该参数通过 ByValue 接受字符串对象类型和管道输入。因此,运行类似 'C:\Windows' | Get-ChildItem 返回 C:\Windows 目录中的所有文件,因为 C:\Windows 是一个字符串。

按属性名称

命令参数不接受整个对象,而是接受该对象的单个属性。它不是通过查看对象类型而是通过属性名称来实现此目的。

Get-Process cmdlet 有一个 Name 参数,该参数设置为接受管道输入 ByPropertyName。当您将具有 Name 属性的对象传递给 Get-Process cmdlet 时,例如 [pscustomobject]@{Name='firefox'} | Get-Process,PowerShell 将传入对象上的 Name 属性匹配或绑定到 Name 参数并使用该值。

发现支持管道的命令参数

如前所述,并非每个命令都支持管道输入。命令作者必须在开发中创建该功能。该命令必须至少有一个支持管道的参数,放置 ByValueByPropertyName

如何知道哪些命令及其参数支持管道输入?您可以通过反复试验来尝试,但使用 Get-Help 命令使用 PowerShell 帮助系统有更好的方法。

Get-Help <COMMAND> -Parameter <PARAMETER>

例如,请查看下面的 Get-ChildItem cmdlet 参数 Path。可以看到它支持两种类型的管道输入。

[玩转系统] 了解 PowerShell 管道和创建函数

一旦您知道哪些命令参数支持管道输入,您就可以利用该功能,如下所示。

# none-pipeline call
Get-ChildItem -Path 'C:\Program Files', 'C:\Windows'

# pipeline call
'C:\Program Files', 'C:\Windows' | Get-ChildItem

但是,另一方面,Get-Service 上的 DisplayName 参数不支持管道输入。

[玩转系统] 了解 PowerShell 管道和创建函数

构建您自己的管道功能

尽管标准 PowerShell cmdlet 支持管道输入,但这并不意味着您无法利用该功能。值得庆幸的是,您也可以构建接受管道输入的函数。

为了进行演示,我们从一个名为 Get-ConnectionStatus 的现有函数开始。

  • 该函数有一个名为 ComputerName 的参数(不接受管道输入),它允许您向其传递一个或多个字符串。 您可以看出 ComputerName 参数不接受管道输入,因为它没有定义为参数属性 ([Parameter()])。
  • 然后,该函数读取每个字符串并对每个字符串运行 Test-Connection cmdlet。
  • 对于传递的每个字符串计算机名称,它会返回一个具有 ComputerNameStatus 属性的对象。
function Get-ConnectionStatus
{
    [CmdletBinding()]
    param
    (
        [Parameter()] ## no pipeline input
        [string[]]$ComputerName
    )

    foreach($c in $ComputerName)
    {
        if(Test-Connection -ComputerName $c -Quiet -Count 1)
        {
            $status = 'Ok'
        }
        else
        {
            $status = 'No Connection'
        }

        [pscustomobject]@{
            ComputerName = $c
            Status = $status
        }
    }
}

然后,您可以通过向 ComputerName 参数传递一个或多个主机名或 IP 地址来调用 Get-ConnectionStatus,如下所示。

Get-ConnectionStatus -ComputerName '127.0.0.1', '192.168.1.100'

第 1 步:允许管道输入

要使该函数接受管道输入,必须首先定义适当的参数属性。此属性可以是 ValueFromPipeline 来接受管道输入 ByValue,也可以是 ValueFromPipelineByPropertyName 来接受管道输入 ByPropertyName。

对于本示例,请在 [Parameter()] 定义的括号内添加 ValueFromPipeline 参数属性,如下所示。

 [Parameter(ValueFromPipeline)]
 [string[]]$ComputerName

此时,从技术上来说,这就是您所要做的全部事情。 Get-ConnectionStatus 函数现在会将传递给它的任何字符串对象绑定到 ComputerName 参数。但是,即使发生参数绑定并不意味着函数会用它做任何有意义的事情。

第 2 步:添加进程块

当您希望 PowerShell 处理来自管道的所有对象时,接下来必须添加一个 Process 块。该块告诉 PowerShell 处理来自管道的每个对象。

如果没有 Process 块,PowerShell 将仅处理来自管道的第一个对象。 Process 块告诉 PowerShell 继续处理对象。

通过包含该函数的所有功能来添加一个 Process 块,如下所示。

function Get-ConnectionStatus
{
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline)]
        [string[]]$ComputerName
    )

    Process ## New Process block
    {
        foreach($c in $ComputerName)
        {
            if(Test-Connection -ComputerName $c -Quiet -Count 1)
            {
                $status = 'Ok'
            }
            else
            {
                $status = 'No Connection'
            }

            [pscustomobject]@{
                ComputerName = $c
                Status = $status
            }
        }
    } ## end Process block
}

始终从 Process 块内发送输出。从Process块内发送输出将对象“流”到管道,允许其他命令从管道接受这些对象。

将对象传递到 PowerShell 管道

在上面的函数中定义了 Process 块后,您现在可以调用该函数,通过管道将值传递给 ComputerName 参数,如下所示。

Get-ConnectionStatus -ComputerName '127.0.0.1', '192.168.1.100'
## or
'127.0.0.1', '192.168.1.100' | Get-ConnectionStatus

此时,您可以利用管道的真正威力并开始将更多命令合并到组合中。例如,您可能有一个文本文件 C:\Test\computers.txt,其中一行 IP 地址通过新行分隔,如下所示。

127.0.0.1
192.168.1.100

然后,您可以使用 Get-Content cmdlet 读取文本文件中的每个 IP 地址,并将它们直接传递给 Get-ConnectionStatus 函数。

Get-Content -Path C:\Test\computers.txt | Get-ConnectionStatus 

将此设置更进一步,您可以将 Get-ConnectionStatus 返回的对象直接通过管道传递给 ForEach-Object cmdlet。

代码如下:

  • 读取文本文件中的所有计算机名称并将它们传递给 Get-ConnectionStatus 函数。
  • Get-ConnectionStatus 处理每个计算机名称并返回一个具有属性 ComputerNameStatus 的对象。
  • 然后,Get-ConnectionStatus 将每个对象传递给 ForEach-Object cmdlet,该 cmdlet 随后返回一个颜色为青色且具有人类可读状态的字符串。
Get-Content -Path C:\Test\computers.txt |
Get-ConnectionStatus |
ForEach-Object { Write-Host "$($_.ComputerName) connection status is: $($_.Status)" -ForegroundColor Cyan }

如果 ComputerName 参数上未启用管道输入,或者 Get-ConnectionStatus 未在 Process 块中返回对象,在处理完所有对象(IP 地址)之前,PowerShell 不会向控制台返回任何状态。

按属性名称绑定管道

到目前为止,Get-ConnectionStatus cmdlet 设置为通过接受诸如 '127.0.0.1', ' 之类的字符串数组来接受管道输入 ByValue (ValueFromPipeline) 192.168.1.100'。如果输入是从 CSV 文件与 IP 地址文本文件接收的,此函数是否也能按预期工作?

也许您有一个如下所示的 CSV 文件,位于 C:\Test\pc-list.csv

ComputerName,Location
127.0.0.1,London
192.168.1.100,Paris

请注意,CSV 文件中的 ComputerName 字段与 Get-ConnnectionStatus ComputerName 参数的名称相同。

如果您尝试导入 CSV 并将其通过管道传递到 Get-ConnectionStatus,该函数会在 ComputerName 列中返回意外结果。

Import-Csv -Path 'C:\Test\pc-list.csv' | Get-ConnectionStatus

ComputerName                                  Status       
------------                                  ------       
@{ComputerName=127.0.0.1; Location=London}    No Connection
@{ComputerName=192.168.1.100; Location=Paris} No Connection

你能猜出出了什么问题吗?毕竟,参数名称确实匹配,那么为什么 PowerShell 管道不将 Import-CSV 返回的输出绑定到 Get-ConnectionStatus 上的 ComputerName 参数?因为您需要参数属性ValueFromPipelineByPropertyName

截至目前,该函数的 ComputerName 参数的参数定义类似于 [Parameter(ValueFromPipeline)]。因此,您必须添加 ValueFromPipelineByPropertyName 来设置 ComputerName 参数以支持输入 ByPropertyName,如下所示。

    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

一旦获得管道支持 ByPropertyName,您就可以告诉 PowerShell 开始查看对象属性名称和对象类型。进行此更改后,您应该会看到预期的输出。

PS> Import-Csv -Path 'C:\Test\pc-list.csv' | Get-ConnectionStatus

ComputerName  Status       
------------  ------       
127.0.0.1     Ok           
192.168.1.100 No Connection

概括

在本教程中,您了解了 PowerShell 管道的工作原理、它如何绑定参数,甚至如何创建支持 PowerShell 管道的您自己的函数。

尽管函数可以在没有管道的情况下工作,但它们不会将对象从一个命令“流”到另一个命令并简化代码。

您能想到您编写的或即将编写的一个函数可以通过使其管道就绪而受益吗?

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯