Как Select-Object останавливает конвейер в PowerShell v3?

В 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 (Примечание: переключатель -Wait для Select-Object позволяет всем элементам достичь блока 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 не требует ни одного из этих методов, поэтому я надеялся, что есть способ остановить конвейер из одной точки в конвейере.


person Rynant    schedule 05.01.2012    source источник
comment
Итак, вы ищете StopUpstreamCommandsException, но вы не можете его использовать, поскольку оно является внутренним. Вот предложение для подключения к MS: connect.microsoft.com/PowerShell/feedback/details/768650/   -  person Lars Truijens    schedule 05.01.2014
comment
Спасибо, @LarsTruijens за то, что указал мне на это; Я проголосовал за.   -  person Rynant    schedule 06.01.2014


Ответы (3)


Прочтите этот пост о том, как отменить конвейер:
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
person Shay Levy    schedule 05.01.2012
comment
Насколько я могу судить, это вызывает исключение StopUpstreamCommandsException, что очень похоже на то, что делает Тобиас в упомянутом мною сообщении. - person Shay Levy; 06.01.2012
comment
Но в отличие от исключения PipelineStoppedException Select-Object не препятствует выполнению последующих команд. Я хотел бы иметь возможность останавливать конвейер, не требуя, чтобы пользователь знал, что он должен обернуть конвейер в do-while или try-catch, но я полагаю, что нельзя использовать StopUpstreamCommandsException, поскольку это частный тип. - person Rynant; 17.01.2012

Попробовав несколько методов, включая выброс StopUpstreamCommandsException, ActionPreferenceStopException и PipelineClosedException, вызов $ PSCmdlet.ThrowTerminatingError и $ ExecutionContext.Host.Runspace.GetCurrentlyRunningPipeline (). Stopper.set_IsS Itopping, наконец, был найден только что использовавший объект select-only был найден ($ true). вещь, которая не прервала весь сценарий (а не только конвейер). [Обратите внимание, что для некоторых из упомянутых выше элементов требуется доступ к закрытым членам, к которым я получил доступ через отражение.]

# 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

Код PowerShell для получения значения PipelineProcessor посредством отражения:

$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))
person Χpẘ    schedule 06.06.2013
comment
Это не то, что я ищу. 1..5| select -First 3 | measure -Sum возвращает результат, а 1..5| %{if($_ -ge 4) {stop-pipeline}} | measure -Sum - нет. Я хочу, чтобы новые элементы не отправлялись по конвейеру, но чтобы конвейер завершил обработку. - person Rynant; 06.06.2013
comment
Можете ли вы включить код для функций invoke-method и select-properties? Предоставленный код не будет работать без этих функций. - person Rynant; 12.06.2013
comment
Я написал invoke-method и select-properties на работе, поэтому мне пришлось бы пройти через весь процесс, чтобы получить разрешение на их публикацию. Однако я добавлю в этот пример код отражения PS, который поможет вам в большинстве случаев. - person Χpẘ; 20.06.2013

Я знаю, что выброс PipelineStoppedException останавливает конвейер. В следующем примере будет имитироваться то, что вы видите с Select -first 1 в v3.0, в v2.0:

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"
person manojlds    schedule 06.01.2012
comment
У вас есть опечатка: $ fist ›$ first. И «новый» должен быть новым-объектом. - person Shay Levy; 06.01.2012
comment
Проблема, с которой я сталкиваюсь с выдачей исключения PipelineStoppedException, заключается в том, что команды, расположенные ниже по конвейеру, не завершают обработку. Это работает: 1..5| select -first 3| measure, но не работает: 1..5| Select-Improved -first 3| measure - person Rynant; 06.01.2012