Is it possible to terminate or stop a PowerShell pipeline from within a filter

后端 未结 8 2160
無奈伤痛
無奈伤痛 2020-12-06 06:27

I have written a simple PowerShell filter that pushes the current object down the pipeline if its date is between the specified begin and end date. The objects coming down

相关标签:
8条回答
  • 2020-12-06 06:31

    Here's an - imperfect - implementation of a Stop-Pipeline cmdlet (requires PS v3+), gratefully adapted from this answer:

    #requires -version 3
    Filter Stop-Pipeline {
      $sp = { Select-Object -First 1 }.GetSteppablePipeline($MyInvocation.CommandOrigin)
      $sp.Begin($true)
      $sp.Process(0)
    }
    
    # Example
    1..5 | % { if ($_ -gt 2) { Stop-Pipeline }; $_ } # -> 1, 2
    

    Caveat: I don't fully understand how it works, though fundamentally it takes advantage of Select -First's ability to stop the pipeline prematurely (PS v3+). However, in this case there is one crucial difference to how Select -First terminates the pipeline: downstream cmdlets (commands later in the pipeline) do not get a chance to run their end blocks.
    Therefore, aggregating cmdlets (those that must receive all input before producing output, such as Sort-Object, Group-Object, and Measure-Object) will not produce output if placed later in the same pipeline; e.g.:

    # !! NO output, because Sort-Object never finishes.
    1..5 | % { if ($_ -gt 2) { Stop-Pipeline }; $_ } | Sort-Object
    

    Background info that may lead to a better solution:

    Thanks to PetSerAl, my answer here shows how to produce the same exception that Select-Object -First uses internally to stop upstream cmdlets.

    However, there the exception is thrown from inside the cmdlet that is itself connected to the pipeline to stop, which is not the case here:

    Stop-Pipeline, as used in the examples above, is not connected to the pipeline that should be stopped (only the enclosing ForEach-Object (%) block is), so the question is: How can the exception be thrown in the context of the target pipeline?

    0 讨论(0)
  • 2020-12-06 06:35

    It is not possible to stop an upstream command from a downstream command.. it will continue to filter out objects that do not match your criteria, but the first command will process everything it was set to process.

    The workaround will be to do more filtering on the upstream cmdlet or function/filter. Working with log files makes it a bit more comoplicated, but perhaps using Select-String and a regular expression to filter out the undesired dates might work for you.

    Unless you know how many lines you want to take and from where, the whole file will be read to check for the pattern.

    0 讨论(0)
  • 2020-12-06 06:44

    Another option would be to use the -file parameter on a switch statement. Using -file will read the file one line at a time, and you can use break to exit immediately without reading the rest of the file.

    switch -file $someFile {
      # Parse current line for later matches.
      { $script:line = [DateTime]$_ } { }
      # If less than min date, keep looking.
      { $line -lt $minDate } { Write-Host "skipping: $line"; continue }
      # If greater than max date, stop checking.
      { $line -gt $maxDate } { Write-Host "stopping: $line"; break }
      # Otherwise, date is between min and max.
      default { Write-Host "match: $line" }
    }
    
    0 讨论(0)
  • 2020-12-06 06:47

    Try these filters, they'll force the pipeline to stop after the first object -or the first n elements- and store it -them- in a variable; you need to pass the name of the variable, if you don't the object(s) are pushed out but cannot be assigned to a variable.

    filter FirstObject ([string]$vName = '') {
     if ($vName) {sv $vName $_ -s 1} else {$_}
     break
    }
    
    filter FirstElements ([int]$max = 2, [string]$vName = '') {
     if ($max -le 0) {break} else {$_arr += ,$_}
     if (!--$max) {
      if ($vName) {sv $vName $_arr -s 1} else {$_arr}
      break
     }
    }
    
    # can't assign to a variable directly
    $myLog = get-eventLog security | ... | firstObject
    
    # pass the the varName
    get-eventLog security | ... | firstObject myLog
    $myLog
    
    # can't assign to a variable directly
    $myLogs = get-eventLog security | ... | firstElements 3
    
    # pass the number of elements and the varName
    get-eventLog security | ... | firstElements 3 myLogs
    $myLogs
    
    ####################################
    
    get-eventLog security | % {
     if ($_.timegenerated -lt (date 11.09.08) -and`
      $_.timegenerated -gt (date 11.01.08)) {$log1 = $_; break}
    }
    
    #
    $log1
    
    0 讨论(0)
  • 2020-12-06 06:50

    It is possible to break a pipeline with anything that would otherwise break an outside loop or halt script execution altogether (like throwing an exception). The solution then is to wrap the pipeline in a loop that you can break if you need to stop the pipeline. For example, the below code will return the first item from the pipeline and then break the pipeline by breaking the outside do-while loop:

    do {
        Get-ChildItem|% { $_;break }
    } while ($false)
    

    This functionality can be wrapped into a function like this, where the last line accomplishes the same thing as above:

    function Breakable-Pipeline([ScriptBlock]$ScriptBlock) {
        do {
            . $ScriptBlock
        } while ($false)
    }
    
    Breakable-Pipeline { Get-ChildItem|% { $_;break } }
    
    0 讨论(0)
  • 2020-12-06 06:50

    Not sure about your exact needs, but it may be worth your time to look at Log Parser to see if you can't use a query to filter the data before it even hits the pipe.

    0 讨论(0)
提交回复
热议问题