【问题标题】:trying to write powershell cmdlet accepting pipelined input尝试编写接受流水线输入的 powershell cmdlet
【发布时间】:2021-06-04 13:48:45
【问题描述】:

我正在尝试了解 powershell 并将函数编写为 cmdlet,在其中一篇文章中找到了以下代码示例,但它似乎不想作为 cmdlet 工作,即使它有 [cmdletbinding()] 声明文件的顶部。

当我尝试做类似的事情时

1,2,3,4,5 | .\measure-data 

它返回空响应(如果我在文件底部调用它并运行文件本身,该函数本身就可以正常工作)。

这是我正在使用的代码,任何帮助将不胜感激:)

Function Measure-Data {
    <#
    .Synopsis
    Calculate the median and range from a collection of numbers
    .Description
    This command takes a collection of numeric values and calculates the
    median and range. The result is written as an object to the pipeline.
    .Example
    PS C:\> 1,4,7,2 | measure-data

    Median                                    Range
    ------                                    -----
    3                                        6

    .Example
    PS C:\> dir c:\scripts\*.ps1 | select -expand Length | measure-data

    Median                                    Range
    ------                                    -----
    1843                                   178435
    #>

    [cmdletbinding()]
    Param (
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
        [ValidateRange([int64]::MinValue,[int64]::MaxValue)]
        [psobject]$InputObject 
    )
    
    Begin {
        #define an array to hold incoming data
        Write-Verbose "Defining data array"
        $Data=@()
    } #close Begin
    
    Process {
        #add each incoming value to the $data array
        Write-Verbose "Adding $inputobject"
        $Data+=$InputObject
    } #close process
    
    End {
        #take incoming data and sort it
        Write-Verbose "Sorting data"
        $sorted = $data | Sort-Object
    
        #count how many elements in the array
        $count = $data.Count
        Write-Verbose "Counted $count elements"
        
        #region calculate median
    
        if ($sorted.count%2) {
            <#
            if the number of elements is odd, add one to the count
            and divide by to get middle number. But arrays start
            counting at 0 so subtract one
            #>
            Write-Verbose "processing odd number"            
            [int]$i = (($sorted.count+1)/2-1)            
            #get the corresponding element from the sorted array
            $median = $sorted[$i]    
        }
        else {
            <#
            if number of elements is even, find the average
            of the two middle numbers
            #>
            Write-Verbose "processing even number"
            $i = $sorted.count/2
            #get the lower number
            $x = $sorted[$i-1]
            #get the upper number
            $y = $sorted[-$i]
            #average the two numbers to calculate the median
            $median = ($x+$y)/2
        } #else even
    
        #endregion
    
        #region calculate range

        Write-Verbose "Calculating the range"
        $range = $sorted[-1] - $sorted[0]
    
        #endregion
    
        #region write result

        Write-Verbose "Median = $median"
        Write-Verbose "Range = $range"
        #define a hash table for the custom object
        $hash = @{Median=$median;Range=$Range}
    
        #write result object to pipeline
        Write-Verbose "Writing result to the pipeline"
        New-Object -TypeName PSobject -Property $hash
    
        #endregion
    } #close end
} #close measure-data

这是我从中获取代码的文章: https://mcpmag.com/articles/2013/10/15/blacksmith-part-4.aspx

编辑:也许我应该添加这篇文章前面部分的代码版本工作得很好,但是在添加了所有使它成为正确 cmdlet 的东西之后,比如帮助部分和详细行,这个东西就是不想工作,我相信缺少一些东西,我觉得这可能是因为它是为 powershell 3 编写的,我正在 win 10 ps 5-point 上测试它,但老实说,我什至不知道我在哪个方向应该寻找,所以才向你求助

【问题讨论】:

    标签: powershell pipeline


    【解决方案1】:

    你传递给它的不是一个对象,而是一个整数数组。如果将参数更改为:

        Param (
            [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
            [ValidateRange([int64]::MinValue,[int64]::MaxValue)]
            [Int[]]$InputObject 
        )
    

    现在一切正常:

    PS> 1,2,3,4,5 | Measure-Data
    
    Median Range
    ------ -----
         3     4
    

    【讨论】:

    • 不需要将$InputObject参数声明为数组。 PowerShell 自动将数组元素一个接一个地传递给 ValueFromPipeline 参数。
    • 那个酱?
    【解决方案2】:

    代码没有任何问题(除了可能的优化之外),但是你调用它的方式不起作用:

    1,2,3,4,5 | .\measure-data
    

    当您调用包含命名函数的脚本文件时,预计“什么都不会发生”。实际上,脚本运行,但 PowerShell 不知道它应该调用哪个函数(可能有多个)。所以它只是运行函数之外的任何代码。

    您有两种解决问题的方法:

    选项 1

    删除function 关键字和属于它的花括号。保留[cmdletbinding()]Param 部分。

    [cmdletbinding()]
    Param (
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
        [ValidateRange([int64]::MinValue,[int64]::MaxValue)]
        [psobject]$InputObject 
    )
    
    Begin {
        # ... your code ...
    } #close Begin
    
    Process {
        # ... your code ...
    } #close process
    
    End {
        # ... your code ...
    }
    

    现在脚本本身就是“函数”,可以这样调用:

    1,2,3,4,5 | .\measure-data
    

    选项 2

    把脚本变成一个模块。基本上你只需要用 .psm1 扩展名保存它(there is more to it,但对于开始它就足够了)。

    在要使用函数的脚本中,您必须先导入模块,然后才能使用它的函数。如果模块没有安装,可以通过指定完整路径来导入。

    # Import module from directory where current script is located
    Import-Module $PSScriptRoot\measure-data.psm1
    
    # Call a function of the module
    1,2,3,4,5 | Measure-Data
    

    模块是在单个脚本文件中有多个函数时的方式。当一个函数被多次调用时效率也会更高,因为 PowerShell 只需要解析一次(它会记住 Import-Module 调用)。

    【讨论】:

    • 非常感谢,还有一件让我头疼的事情:有没有办法将函数目录放在路径变量中,这样我就可以通过执行 1、2、3 来调用函数, 4,5 |测量数据?这并不是说我没有它就活不下去,或者我需要它来完成一项特定的任务,只是想看看我的选择是什么,当我试图通过“编辑环境变量”窗格将带有所述脚本的目录添加到路径中并使用它时以这种方式,powershell 返回了通常的错误' The term 'measure-data' is not Recognized as the name of a cmdlet...'
    • @perfect_null 对于选项 1,将脚本目录添加到 PATH 环境变量应该可以。也许您需要重新登录到 Windows 才能使其生效。对于选项 2,请参阅 How to install a module
    【解决方案3】:

    它按原样工作,您只需要正确调用它。由于代码现在是一个函数,你不能像以前代码直接在文件中那样调用它

    # method when code is directly in file with no Function Measure-Data {}
    1,2,3,4,5 | .\measure-data 
    

    现在您已经定义了函数,您需要为文件添加点源代码,以便将您的函数加载到内存中。然后你可以通过它的名字来调用你的函数(它恰好与文件名相同,但不一定是)

    # Load the functions by dot-sourcing
    . .\measure-data.ps1
    
    # Use the function
    1,2,3,4,5 | Measure-Data
    

    【讨论】:

    • 是的,点源是另一种方式。我没有在回答中提到它,因为我认为这是不好的做法(它还导入了在函数之外定义的所有变量,这通常是不需要的,因为这些通常是脚本的“私有”)。它可能有它的用途,但是对于模块化代码,.psm1 模块提供了更好的封装。
    • 感谢您指出此类解决方案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-27
    • 2012-09-10
    • 2020-06-27
    • 1970-01-01
    • 2015-06-21
    相关资源
    最近更新 更多