Home > Software engineering >  Powershell piping multiple PSCustomObjects drops all but first object
Powershell piping multiple PSCustomObjects drops all but first object

Time:09-08

I'm making some modules to make RESTAPI calls easier for our software (since all resources use different logic to how I can find them and how they should be called but that's a separate story). I'm making Get-, Set-, New-, and Remove- for each resource and just found that you can't pipe more than one object to modules that takes arrays from the pipeline. Here's an illustration of the problem that you can copy-paste into your environment:

function Get-MyTest {

    param(
        [string[]]$MyInput
    )


    [array]$Output = @()
    $i=0

    foreach($Item in $MyInput){

        $i  

        $Output  = [pscustomobject]@{
            Name = $Item
            Id = $i
        }

    }

    return $Output

}

function Get-IncorrectResults {

    param(
        [parameter(ValueFromPipeline)][array]$MyIncorrectInput
    )

    foreach ($Item in $MyIncorrectInput) {
        
        Write-Host "Current object: $Item `n "

    }

}

The Get module can return more than one object. Each object returned is a PSCustomObject, so if there are more than one returned it becomes an array of PSCustomObjects. Here's the problem:

This works:

$Results = Get-MyTest -MyInput "Test1","Test2","Test3"
Get-IncorrectResults -MyIncorrectInput $Results

This only returns the first item:

$Results = Get-MyTest -MyInput "Test1","Test2","Test3"
$Results | Get-IncorrectResults

If the Get part returns more than one object, only the first object is passed on to the Remove-MyModule. I tried changing the parameter definition from [pscustomobject[]] to [array] but it's the same result. What is the reason for dropping all but the first array item when piping but not when using it as a parameter?

CodePudding user response:

The structure of an advanced PowerShell function is as follows:

function Function-Name {
  param(
    <# parameter definitions go here#>
  )

  begin {
    <# this runs once when the pipeline starts #>
  }

  process {
    <# this runs once for every input item piped to the function #>
  }

  end {
    <# this runs once at the end of the pipeline #>
  }
}

When you omit the begin/process/end blocks, PowerShell assumes your function body is really the end block - so this function definition:

function Function-Name {
  param()

  Do-Something
}

... is the exact same as:

function Function-Name {
  param()

  begin {}

  process {}

  end {Do-Something}
}

Notice that the process block - the only piece of the function body that repeats with new input - is assumed empty, and the Do-Something statement will not be executed until after the last input item has been received.

This is why it looks like it "drops" everything but the last input object.

Place the code that consumes the pipeline-bound variable inside the process block and it'll work:

Remove-MyModule{

    param(
        [parameter(ValueFromPipeline)][pscustomobject[]]$MyModules
    )

    process {
        foreach($Module in $MyModules){
            Invoke-RestMethod -Method Delete -Uri "MyURL" -Body (ConvertTo-Json $Module) -UseDefaultCredentials
        }
    }
}

For more information about the syntax and semantics of advanced functions, see the about_Functions_Advanced_Methods help file

  • Related