powershell 如何在使用开始/Process/End块时一次获取所有管道对象?

rks48beu  于 2023-04-30  发布在  Shell
关注(0)|答案(2)|浏览(206)

我被困在一个学习项目上,我已经开始了,这里的目标是让我学习整个BeginProcessEnd块的概念
我的项目包含一个函数,它可以让用户打开一个具有特定配置文件的Firefox窗口,并可以通过保存在C:驱动器上的.url文件打开一个或多个网页。这是我目前掌握的情况:

Function Ask-Web{
    [CmdletBinding()]
    Param(
        [Parameter(Position = 1)]
        [ArgumentCompletions('andy','andrew','halley', "hanna")]
        [Array]$FirefoxProfile = 'Andy',
        [Parameter(ValueFromPipeline, Position = 2)]
        [ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
        [Array]$Pages
    )
Begin{
    $FavouritePages = [ordered]@{
    DIYhome     = "https://www.diyUK.com"
    DIYreddit   = "https://www.reddit.com/r/DIY/submit"
    DIYexchange = "https://diy.stackexchange.com/questions/ask"
    # DIYall  Will handle this later
    } 
    
    [array]$Pages = If($Pages -eq "SelectWithFZF"){Get-ChildItem -Path "C:\temp\Ask Pages\" -Filter *.url*| FZF}Else{$FavouritePages."$Pages"}  ;the FZF utility will store all selected items into $Pages variable
    }
End{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages}

}

该函数应该让用户通过参数完成来选择页面,从管道中预先提供网页,或者使用实用程序FZF从文件夹C:\temp\Ask Pages\中进行选择。
在所有示例中,期望用户提供一个或多个页面/URL:

Ask-Web -FirefoxProfile andy -Pages DIYhome,DIYreddit,DIYexchange    ; Open a singe Firefox window, with profile "Andy" and the pages DIYhome,DIYreddit and DIYexchange
Ask-Web -FirefoxProfile hanna -Pages DIYhome                         ; Open a singe Firefox window, with profile "Hanna" and the page DIYhome
Ask-Web -FirefoxProfile andrew -Pages SelectWithFZF                  ; Open a singe Firefox window, with profile "Andrew" and all the pages that were selected with FZF
Get-ChildItem .\Diy -filter *.url* | Ask-Web -FirefoxProfile hanna   ; Open a singe Firefox window, with profile "Hanna" and with all the pages provided by the pipeline

我似乎被卡住的地方是让PowerShell一次给予我所有的管道输入,而不是一次一个或最后一个管道项,这就是我用上面的函数得到的。
如果我使用Process{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages},那么PowerShell将为$Pages中的每个项目打开一个Firefox窗口,而使用End{. 'C:\Program Files\Mozilla Firefox\firefox.exe' -p $FirefoxProfile $Pages},只有$Pages中的最后一个项目被打开。
我试过:

Function Ask-Web{
    [CmdletBinding()]
    Param(
        [Parameter(Position = 1)]
        [ArgumentCompletions('andy','andrew','halley', "hanna")]
        [Array]$FirefoxProfile = 'Andy',
        [Parameter(ValueFromPipeline, Position = 2)]
        [ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
        [Array]$Pages
    )
    Process{[Array]$Pages += @($input)}  
    End{$Pages}
}

对于Ask-Web -FirefoxProfile andy -Pages DIYhome,DIYreddit,DIYexchange,在End块中,我得到:

DIYhome
DIYreddit
DIYexchange

但是对于End块中的'DIYhome','DIYreddit','DIYexchange'|Ask-Web -FirefoxProfile andy,我得到:

DIYexchange
DIYexchange

如果我放弃所有BeginProcessEnd块:

Function Ask-Web{
    [CmdletBinding()]
    Param(
        [Parameter(Position = 1)]
        [ArgumentCompletions('andy','andrew','halley', "hanna")]
        [Array]$FirefoxProfile = 'Andy',
        [Parameter(ValueFromPipeline, Position = 2)]
        [ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
        [Array]$Pages
    )
    $input
}

我得到了一致的结果,数组$Pages的所有元素都被一次传递,不管用户如何提供它们。我怎么能做同样的,但与BeginProcessEnd块?
任何帮助将不胜感激!

zfycwa2u

zfycwa2u1#

你有两个选择,推荐的一个是用List<T>收集通过管道的所有输入,这是在你的函数的process块中完成的。然后在收集所有管道输入后,您可以在end块中启动Firefox。
使用List<T>而不是System.Array@()+=)来收集管道输入的主要原因是因为数组的大小是固定的,并且PowerShell必须在每次添加新元素时重新创建它,这使得它非常低效。请参阅this answer和PowerShell脚本性能注意事项- Array addition以了解更多详细信息。

function Test-Pipeline {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline)]
        [Array] $InputObject
    )

    begin {
        $list = [System.Collections.Generic.List[object]]::new()
    }
    process {
        $list.AddRange($InputObject)
    }
    end {
        if($list.Count) {
            # do stuff here with `$list`
            $list.ToArray()
        }
    }
}

Get-ChildItem | Test-Pipeline

在这种情况下,List<T>是必要的,因为PowerShell将通过管道一次传递一个对象,这就是所谓的一次一个处理。在这种情况下,不建议使用$input,因为1.你的函数是一个advanced one,不建议在高级函数中使用这个自动变量。2.使用$input意味着你的函数只能在管道中工作,除非你绕过它。
第二个选项,也是不推荐的选项,是通过管道传递Get-ChildItem的结果,而不枚举它。为此,您可以使用Write-Output -NoEnumerate或逗号运算符,

function Test-Pipeline {
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline)]
        [Array] $InputObject
    )

    end {
        $InputObject
    }
}

# option 1:
Write-Output (Get-ChildItem) -NoEnumerate | Test-Pipeline
# option 2:
, (Get-ChildItem) | Test-Pipeline
k5ifujac

k5ifujac2#

您的问题是您的$Pages变量存在作用域问题。
当通过管道传递时,$Pages变量被重置为枚举的最新值。这就是为什么你只得到最后一个值两次。基本上,Process块每次都会看到一个新的$Pages,所以在最后一轮中,您将添加最新的$input
我认为最简单的解决方案是引入一个新变量,然后在End块中使用:

Function Ask-Web{
    [CmdletBinding()]
    Param(
        [Parameter(Position = 1)]
        [ArgumentCompletions('andy','andrew','halley', "hanna")]
        [Array]$FirefoxProfile = 'Andy',
        [Parameter(ValueFromPipeline, Position = 2)]
        [ArgumentCompletions('DIYhome','DIYreddit','DIYexchange','DIYall', 'SelectWithFZF')]
        [Array]$Pages
    )
    Process{
        write-host "Process: $Pages"
        [Array]$AllPages += $Pages
        }  
    End{
        
        $AllPages }
}

相关问题