【问题标题】:How does Select-Object stop the pipeline in PowerShell v3?Select-Object 如何在 PowerShell v3 中停止管道?
【发布时间】:2012-02-03 16:09:18
【问题描述】:

在 PowerShell v2 中,以下行:

1..3| foreach { Write-Host "Value : $_"; $_ }| select -First 1

将显示:

Value : 1
1
Value : 2
Value : 3

由于所有元素都被推下管道。但是,在 v3 中,上述行仅显示:

Value : 1
1

管道在 2 和 3 被发送到 Foreach-Object 之前停止(注意:Select-Object-Wait 开关允许所有元素到达 foreach 块)。

Select-Object 如何停止管道,我现在可以从 foreach 或我自己的函数停止管道吗?

编辑:我知道我可以将管道包装在 do...while 循环中并继续退出管道。我还发现在 v3 中我可以做这样的事情(它在 v2 中不起作用):

function Start-Enumerate ($array) {
    do{ $array } while($false)  
}

Start-Enumerate (1..3)| foreach {if($_ -ge 2){break};$_}; 'V2 Will Not Get Here'

但是Select-Object 不需要这些技术中的任何一种,所以我希望有一种方法可以从管道中的一个点停止管道。

【问题讨论】:

标签: powershell powershell-3.0


【解决方案1】:

查看这篇文章,了解如何取消管道:
http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx

在 PowerShell 3.0 中,这是一项引擎改进。从 CTP1 示例文件夹('\Engines Demos\Misc\ConnectBugFixes.ps1'):

# Connect Bug 332685
# Select-Object optimization
# Submitted by Shay Levi
# Connect Suggestion 286219
# PSV2: Lazy pipeline - ability for cmdlets to say "NO MORE"
# Submitted by Karl Prosser

# Stop the pipeline once the objects have been selected
# Useful for commands that return a lot of objects, like dealing with the event log

# In PS 2.0, this took a long time even though we only wanted the first 10 events
Start-Process powershell.exe -Args '-Version 2 -NoExit -Command Get-WinEvent | Select-Object -First 10'

# In PS 3.0, the pipeline stops after retrieving the first 10 objects
Get-WinEvent | Select-Object -First 10

【讨论】:

  • 据我所知,它会抛出一个 StopUpstreamCommandsException 异常,这与 Tobias 在我提到的帖子中所做的非常相似。
  • 但与 PipelineStoppedException 不同,Select-Object 不会阻止下游命令完成。我希望能够在不要求用户知道他们必须将管道包装在 do-while 或 try-catch 中的情况下停止管道,但我认为不能使用 StopUpstreamCommandsException,因为它是私有类型。跨度>
【解决方案2】:

在尝试了几种方法,包括抛出 StopUpstreamCommandsException、ActionPreferenceStopException 和 PipelineClosedException,调用 $PSCmdlet.ThrowTerminatingError 和 $ExecutionContext.Host.Runspace.GetCurrentlyRunningPipeline().stopper.set_IsStopping($true) 之后,我终于发现只使用 select-object是唯一没有中止整个脚本的东西(与管道相比)。 [请注意,上面提到的某些项目需要访问私有成员,我通过反射访问。]

# This looks like it should put a zero in the pipeline but on PS 3.0 it doesn't
function stop-pipeline {
  $sp = {select-object -f 1}.GetSteppablePipeline($MyInvocation.CommandOrigin)
  $sp.Begin($true)
  $x = $sp.Process(0) # this call doesn't return
  $sp.End()
}

新方法基于 OP 的评论。不幸的是,这种方法要复杂得多,并且使用私有成员。另外我不知道这有多强大 - 我刚刚让 OP 的示例工作并停在那里。所以FWIW:

# wh is alias for write-host
# sel is alias for select-object

# The following two use reflection to access private members:
#   invoke-method invokes private methods
#   select-properties is similar to select-object, but it gets private properties

# Get the system.management.automation assembly
$smaa=[appdomain]::currentdomain.getassemblies()|
         ? location -like "*system.management.automation*"

# Get the StopUpstreamCommandsException class
$upcet=$smaa.gettypes()| ? name -like "*upstream*"

filter x {
  [CmdletBinding()]
  param(
    [parameter(ValueFromPipeline=$true)]
    [object] $inputObject
  )
  process {
    if ($inputObject -ge 5) {
      # Create a StopUpstreamCommandsException
      $upce = [activator]::CreateInstance($upcet,@($pscmdlet))

      $PipelineProcessor=$pscmdlet.CommandRuntime|select-properties PipelineProcessor
      $commands = $PipelineProcessor|select-properties commands
      $commandProcessor= $commands[0]

      $null = $upce.RequestingCommandProcessor|select-properties *

      $upce.RequestingCommandProcessor.commandinfo =  
          $commandProcessor|select-properties commandinfo

      $upce.RequestingCommandProcessor.Commandruntime =  
          $commandProcessor|select-properties commandruntime

      $null = $PipelineProcessor|
          invoke-method recordfailure @($upce, $commandProcessor.command)

      1..($commands.count-1) | % {
        $commands[$_] | invoke-method DoComplete
      }

      wh throwing
      throw $upce
    }
    wh "< $inputObject >"

    $inputObject
  } # end process
  end {
    wh in x end
  }
} # end filter x

filter y {
  [CmdletBinding()]
  param(
    [parameter(ValueFromPipeline=$true)]
    [object] $inputObject
  )
  process {
    $inputObject
  }
  end {
    wh in y end
  }
}

1..5| x | y | measure -Sum

通过反射检索 PipelineProcessor 值的 PowerShell 代码:

$t_cmdRun = $pscmdlet.CommandRuntime.gettype()
# Get pipelineprocessor value ($pipor)
$bindFlags = [Reflection.BindingFlags]"NonPublic,Instance"
$piporProp = $t_cmdRun.getproperty("PipelineProcessor", $bindFlags )
$pipor=$piporProp.GetValue($PSCmdlet.CommandRuntime,$null)

通过反射调用方法的Powershell代码:

$proc = (gps)[12] # semi-random process
$methinfo = $proc.gettype().getmethod("GetComIUnknown", $bindFlags)
# Return ComIUnknown as an IntPtr
$comIUnknown = $methinfo.Invoke($proc, @($true))

【讨论】:

  • 这不符合我的要求。 1..5| select -First 3 | measure -Sum 返回结果,但 1..5| %{if($_ -ge 4) {stop-pipeline}} | measure -Sum 不返回。我想阻止新项目通过管道发送,但允许管道完成处理。
  • 您能否包含invoke-methodselect-properties 函数的代码?如果没有这些功能,提供的代码将无法工作。
  • 我在工作中编写了invoke-method和select-properties,所以我必须经历一个完整的过程才能获得发布它们的权限。不过,我会在示例中添加一些 PS 反射代码,以帮助您了解大部分情况。
【解决方案3】:

我知道抛出 PipelineStoppedException 会停止管道。以下示例将模拟您在 v3.0 和 v2.0 中使用 Select -first 1 看到的内容:

filter Select-Improved($first) {
    begin{
        $count = 0
    }
    process{
        $_
        $count++
        if($count -ge $first){throw (new-object System.Management.Automation.PipelineStoppedException)}
    }
}

trap{continue}
1..3| foreach { Write-Host "Value : $_"; $_ }| Select-Improved -first 1
write-host "after"

【讨论】:

  • 你有一个错字:$fist > $first。并且“新”应该是新对象。
  • 我在抛出 PipelineStoppedException 时遇到的问题是管道下方的命令无法完成处理。这有效:1..5| select -first 3| measure,但无效:1..5| Select-Improved -first 3| measure
猜你喜欢
  • 1970-01-01
  • 2011-12-06
  • 2019-06-22
  • 1970-01-01
  • 2013-01-08
  • 1970-01-01
  • 1970-01-01
  • 2014-02-13
  • 1970-01-01
相关资源
最近更新 更多