Как 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 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
не требует ни одного из этих методов, поэтому я надеялся, что существует способ остановить конвейер из одной точки в конвейере.
Ответы
Ответ 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
Ответ 2
После нескольких методов, включая throwing StopUpstreamCommandsException, ActionPreferenceStopException и PipelineClosedException, вызывая $PSCmdlet.ThrowTerminatingError и $ExecutionContext.Host.Runspace.GetCurrentlyRunningPipeline(). stopper.set_IsStopping ($ true) Наконец я обнаружил, что просто использование select-object было единственное, что не прервало весь script (по сравнению с только конвейером). [Обратите внимание, что некоторые из упомянутых выше элементов требуют доступа к закрытым членам, к которым я обращался через отображение.]
# 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))
Ответ 3
Я знаю, что бросание PipelineStoppedException останавливает конвейер. Следующий пример будет имитировать то, что вы видите с помощью Select -first 1
в версии 3.0, в версии 2.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"