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?

  • 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: ``` – J. Breneman Sep 14 '22 at 23:30
  • Sorry. I didn't read the post closely enough! :) – Daniel Sep 14 '22 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 – Santiago Squarzon Sep 14 '22 at 23:42
  • Happens something similar to this: `{ foreach($i in $args) { $i; Start-Sleep 1 }}.Invoke(0..2)` – Santiago Squarzon Sep 15 '22 at 00: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 – J. Breneman Sep 15 '22 at 00:16
  • @Daniel All is good. Strangely, even with that pause the winget's show function still did not do it's thing. – J. Breneman Sep 15 '22 at 00:18
  • your last example in comments streams each value and sleeps – Santiago Squarzon Sep 15 '22 at 00:26

2 Answers2

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
}
Doug Maurer
  • 8,090
  • 3
  • 12
  • 13
  • 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 – J. Breneman Sep 15 '22 at 00:06
  • Yeah winget is known for some odd output behavior – Doug Maurer Sep 15 '22 at 01:43
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
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 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! – J. Breneman Sep 19 '22 at 05:38
  • 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. – mklement0 Sep 19 '22 at 12:21
  • 1
    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! – J. Breneman Sep 20 '22 at 03:11