0

I need to use the output from first function to the second function. The output from the first function would be generated little by little but I would expect the 2nd function to consume it immediately when it's available. Can you please help? I use sleep 5 to demonstrate the delay. Below is the code.

Function Test-Service {

  $p = Get-Service | select name, CanStop
  foreach($m in $p)
  {
    $instance = New-Object PSObject -Property @{
    Name = $m.name
    CanStop = $m.CanStop
    }
    write-host $instance
    sleep 5
  }

}

Function Get-Something {
    [CmdletBinding()]
        Param(
            [Parameter(ValueFromPipelineByPropertyName)]
                $instance
        )
        process {
            $name = $instance.name
            $canstop = $instance.canstop
            Write-Host "You passed the parameter $Name $CanStop into the function"
        }
}

The output is as below:

@{Name=AarSvc_f5f1f38; CanStop=True} @{Name=AdobeARMservice; CanStop=True} ... which is the output of the first function "Test-Service". It seems it didn't even received by the Get-Something

Thanks

Update #1: After check @mklement0 and @zett42's feedback I realized I misunderstood the Write-Host vs pipleline.

I then tried a new example as below(Which is more of my real world code) I created a lot of empty .tbd files under current directory according to the $list. I then read the list, get the x and y position and pass the name, x, y to next function to consume The way I ran them is:

Populate-NextIcon -List $instance| Launch-Test

I would expect to see the output from screen periodically but there's no output at all. which means my "Launch-Test" function was not invoked properly.

function Launch-TEST
{
    
    [CmdletBinding()]
    Param(
            [Parameter(ValueFromPipelineByPropertyName )]
                [String]$name,
            [Parameter(ValueFromPipelineByPropertyName )]
                [int]$x,
            [Parameter(ValueFromPipelineByPropertyName )]
                [int]$y              
        )
    
    $prefix = "Launch-"
    $message = "${prefix}: Received ${name}"
    write-host $message
}   
function Populate-NextIcon
{
    param ([System.Collections.ArrayList]$list)
    while($true)
    {
        $icons = Get-ChildItem -path .\*.tbd | Sort-Object LastWriteTime
        if($icons.count -eq 0)
        {
            break
        }
        $t = Get-Date -Format "yyyy_MM_dd_HHmmss"
        $icon = $icons[0]
        $baseName = $icon.BaseName
        Add-Content -Path $icon.FullName -Value $t 
        $i = $list | where-object {$_.name -eq $baseName}
        [PSCustomObject]@{
        Name = $i.name
        x = $i.x
        y = $i.y
        }
        
        $sleep = (Get-Content -Path $icon.FullName | Measure-Object -Line).Lines
        $sleep = $sleep * 5
        sleep $sleep    

    }
    
}

If I ran it as Populate-NextIcon -List $list

I will get below output as expected:

Name            x   y
----            -   -
AAA_PROD    1300 358
BBB_PROD 2068 250
CCC_PROD    1556 358

But if i pipelined it as Populate-NextIcon -List $instance| Launch-Test

Then although i can see these .tbd files got updated there's no output in the screen It seems the output from first function is held up somewhere?

Ginger_Chacha
  • 317
  • 1
  • 11

1 Answers1

2

As stated in the comments, using Write-Host doesn't produce output that can be directly processed further down the pipeline. As the name says, Write-Host writes to the host (aka information stream), but we need to write to the output (aka success) stream instead.

A more idiomatic way would be:

Function Test-Service {

  $p = Get-Service | select name, CanStop
  foreach($m in $p)
  {
    [PSCustomObject]@{
        Name = $m.name
        CanStop = $m.CanStop
    }
    sleep 5
  }
}

By creating a [PSCustomObject] without assigning it to a variable, it will automatically end up as output of the function. This is similar to New-Object PSObject -Property ... but less verbose.


In the first sentence I wrote "Write-Host doesn't produce output that can be directly processed". Emphasis on directly because one could make your original code work like this:

Test-Service 6>&1 | Get-Something

Here the number 6 stands for the information stream (where Write-Host outputs to) and the number 1 stands for the success stream. So we are redirecting the information stream to the success stream which actually merges both streams into one. Now the output of the function can be successfully passed to Get-Something.

About redirection


On a side note, the title of this question is somewhat confusing, because there is nothing asynchronous in normal pipeline processing. While each cmdlet in the pipeline processes its input, no other part of the pipeline is running and the rest of the script continues only after the pipeline processing has finished.

mklement0
  • 382,024
  • 64
  • 607
  • 775
zett42
  • 25,437
  • 3
  • 35
  • 72
  • Interesting - If i use the example it worked but if i used something else it did't – Ginger_Chacha Feb 08 '21 at 10:00
  • Sorry for the confusing. I named it as "Async" as I would expect the producer created an object then the consumer consumed it without waiting for producer to complete all object. This is the pattern I see in the test-service/get-something after the changes were made per the feedback. However in my new example it didn't work. Is it due to the difference of while and foreach? – Ginger_Chacha Feb 08 '21 at 10:25
  • @Ginger_Chacha Yeah, but it still isn't async, because producer has to wait until single output item has been processed by pipeline chain. I can't see an error in your updated code. Maybe it's a testing error? Try to single-step the code in a debugger to see if everything behaves as expected. – zett42 Feb 08 '21 at 10:36
  • thanks @zett42 for the comment. I still have a long way to learn scripting :) Your input is appreciate. I have use the foreach-object loop to resolve my issue temporarily but then something new appeared... – Ginger_Chacha Feb 08 '21 at 23:23