3

I have been working on this for a while and I cannot find this utilization anywhere. I am using a powershell array and the foreach statement: @('program 1','program 2','program 3').foreach{winget show $_ -args}

I then want to have a pause between each one so I added ;sleep 1
This does not work. It pauses for 3s (based on this eg.) and then lists the items. What am I missing here?

7
  • Ok. That certainly addresses the pausing, but I wanted the functionality of sleep rather than timeout. My output: Press enter to continue: Press enter to continue: Press enter to continue: Press enter to continue: Press enter to continue: Press enter to continue: Press enter to continue: Press enter to continue: Commented Sep 14, 2022 at 23:30
  • Sorry. I didn't read the post closely enough! :) Commented Sep 14, 2022 at 23:37
  • .ForEach outputs the collection as whole it doesn't stream values hence why you see all output at once instead of each element followed by it's corresponding sleeping time Commented Sep 14, 2022 at 23:42
  • Happens something similar to this: { foreach($i in $args) { $i; Start-Sleep 1 }}.Invoke(0..2) Commented Sep 15, 2022 at 0:02
  • This nets the same result. Even if I do it as $i = ('1','2','3');foreach($ii in $i){write-output $ii;sleep 1} I get the same result. Sleeps 3 seconds and returns all of $i Commented Sep 15, 2022 at 0:16

2 Answers 2

4

Indeed it doesn't seem to respect the order, I don't know the technical reason why. You could either use a normal ForEach-Object

'program 1','program 2','program 3' | ForEach-Object {
    winget show $_
    sleep 1
}

or force the output to go to the console instead of being "buffered"

('program 1','program 2','program 3').ForEach{
    winget show $_ | Out-Host
    sleep 1
}
Sign up to request clarification or add additional context in comments.

2 Comments

I think that this may be an issue with winget specifically. The output is weird. If I do winget show $_|select * the only thing I get is a list of numbers which I can only assume are length values
Yeah winget is known for some odd output behavior
3

Doug Maurer's helpful answer provides effective solutions.

As for an explanation of the behavior you saw:

The intrinsic .ForEach() method first collects all success output produced by the successive, per-input-object invocations of the script block ({ ... }) passed to it, and only after having processed all input objects outputs the collected results to the pipeline, i.e. the success output stream, in the form of a [System.Collections.ObjectModel.Collection[psobject]] instance.

In other words:

  • Unlike its cmdlet counterpart, the ForEach-Object cmdlet, the .ForEach() method does not emit output produced in its script block instantly to the pipeline.

  • As with any method, success output is only sent to the pipeline when the method returns.

    • Note that non-PowerShell .NET methods only ever produce success output (which is their return value) and only ever communicate failure via exceptions, which become statement-terminating PowerShell errors.

By contrast, the following do take instant effect inside a .ForEach() call's script block:

  • Suspending execution temporarily, such as via a Start-Sleep

  • Forcing instant display (host) output, such as via Out-Host or Write-Host.

    • Note that to-host output with Out-Host prevents capturing the output altogether, whereas Write-Host output, in PowerShell v5+, can only be captured via the information output stream (number 6).
  • Writing to an output stream other than the success output stream, such as via Write-Error, Write-Warning or Write-Verbose -Verbose.

Alternatively, you may use the foreach statement, which, like the ForEach-Object cmdlet, also instantly emits output to the success output stream:

# Stand-alone foreach statement: emits each number right away.
foreach ($i in 1..3) { $i; pause }

# In a pipeline, you must call it via & { ... } or . { ... }
# to get streaming behavior.
# $(...), the subexpression operator would NOT stream, 
# i.e. it would act like the .ForEach() method.
& { foreach ($i in 1..3) { $i; pause } } | Write-Output

3 Comments

Gents, I have success. With a little variation on the ideas provided the problem has been solved. This is my inline version ('XPDP273C0XHQH2','Nlitesoft.NTLite','JRSoftware.InnoSetup').ForEach{cls;$a = winget show $_;write-host $a.Item(1); sleep 1} @mklement0 Absolutely understand this now, voted up your response, but as you said at the outset Doug's answer was fantastic. I am not sure why it worked differently before, but upon the above code it clears the screen, loads just the information that I want (Yay! learned how the .Item() part worked!), and sleeps for 1 second. Thank you both!
Glad to hear it, @JoeyHarlequin; my pleasure. Note that the simpler alternative to $a.Item(1) is $a[1], i.e., the index syntax that is usually used with arrays.
copy that! I have always used the [x], but I keep getting yelled at (by ISE formatting tools) for using aliases and short-cuts. What a world we live in that humans are recoding to make the Machine happy! lol Thanks again for all the help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.