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
@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.
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.