How does Select-Object stop the pipeline in PowerShell v3?

前端 未结 3 1031
温柔的废话
温柔的废话 2020-12-15 10:44

In PowerShell v2, the following line:

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

Would display:

Value         


        
相关标签:
3条回答
  • 2020-12-15 11:06

    After trying several methods, including throwing StopUpstreamCommandsException, ActionPreferenceStopException, and PipelineClosedException, calling $PSCmdlet.ThrowTerminatingError and $ExecutionContext.Host.Runspace.GetCurrentlyRunningPipeline().stopper.set_IsStopping($true) I finally found that just utilizing select-object was the only thing that didn't abort the whole script (versus just the pipeline). [Note that some of the items mentioned above require access to private members, which I accessed via reflection.]

    # 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()
    }
    

    New method follows based on comment from OP. Unfortunately this method is a lot more complicated and uses private members. Also I don't know how robust this - I just got the OP's example to work and stopped there. So 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 code to retrieve PipelineProcessor value through reflection:

    $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 code to invoke method through reflection:

    $proc = (gps)[12] # semi-random process
    $methinfo = $proc.gettype().getmethod("GetComIUnknown", $bindFlags)
    # Return ComIUnknown as an IntPtr
    $comIUnknown = $methinfo.Invoke($proc, @($true))
    
    0 讨论(0)
  • 2020-12-15 11:11

    I know that throwing a PipelineStoppedException stops the pipeline. The following example will simulate what you see with Select -first 1 in v3.0, in 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"
    
    0 讨论(0)
  • 2020-12-15 11:15

    Check this post on how you can cancel a pipeline:
    http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx

    In PowerShell 3.0 it's an engine improvement. From the CTP1 samples folder ('\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
    
    0 讨论(0)
提交回复
热议问题