Search code examples
powershellpipeline

How to convert infinity while loop to a pipeline statement


I'm trying to use PowerShell pipeline for some recurring tasks and checks, like perform certain checks X times or skip forward after the response in pipeline will have different state.

The simplest script I can write to do such checks is something like this:

do {
    $result=Update-ACMEIdentifier dns1 -ChallengeType dns-01
    if($result.Status -ne 'pending')
    { 
        "Hurray"
        break 
    }

    "Still pending"
    Start-Sleep -s 3
} while ($true)

The question is - how can I write this script as a single pipeline. It looks like the only thing I need is infinity pipeline to start with:

  1..Infinity |
    %{ Start-Sleep -Seconds 1 | Out-Null; $_ } |
    %{Update-ACMEIdentifier dns1 -ChallengeType dns-01 } |
    Select -ExpandProperty Status | ?{$_ -eq 'pending'} |
    #some code here to stop infinity producer or stop the pipeline

So is there any simple one-liner, which allows me to put infinity object producer on one side of the pipeline?

Good example of such object may be a tick generator that generates current timestamp into pipeline every 13 seconds


Solution

  • @PetSerAl gave the crucial pointer in a comment on the question: A script block containing an infinite loop, invoked with the call operator (&), creates an infinite source of objects that can be sent through a pipeline:

    & { while ($true) { ... } }

    A later pipeline segment can then stop the pipeline on demand.

    Note:

    • As of PS v5, only Select-Object is capable of directly stopping a pipeline.

      • An imperfect generic pipeline-stopping function can be found in this answer of mine.
    • Using break to stop the pipeline is tricky, because it doesn't just stop the pipeline, but breaks out of any enclosing loop - safe use requires wrapping the pipeline in a dummy loop.

    • Alternatively, a Boolean variable can be used to terminate the infinite producer.

    Here are examples demonstrating each approach:


    A working example with Select-Object -First:

    & { while ($true) { Get-Date; Start-Sleep 1 } } | Select-Object -First 5
    

    This executes Get-Date every second indefinitely, but is stopped by Select-Object after 5 iterations.

    An equivalent example with break and a dummy loop:

    do { 
       & { while ($true) { Get-Date; Start-Sleep 1 } } |
         % { $i = 0 } { $_; if (++$i -eq 5) { break } }  # `break` stops the pipeline and
                                                         # breaks out of the dummy loop
    } while ($false)
    

    An equivalent example with a Boolean variable that terminates the infinite producer:

    & { while (-not $done) { Get-Date; Start-Sleep 1 } } |
      % { $done = $false; $i = 0 } { $_; if (++$i -eq 5) { $done = $true } }
    

    Note how even though $done is only initialized in the 2nd pipeline segment - namely in the ForEach-Object (%) cmdlet's (implicit) -Begin block - that initialization still happens before the 1st pipeline segment - the infinite producer - starts executing.Thanks again, @PetSerAl.