3

I created this function that parses a field for specific text and returns a custom object.

Everything works fine if I use the syntax, Get-MachineUser -VMArray $PassedArray but it doesn't work if I pipe the array $PassedArray | Get-MachinesUser.

I worked with someone on my team and we figured out when we pass the array it is only processing the last entry in the array. I don't mind using the other syntax, but I am curious what error I have that causes piping not to work.

function Get-MachinesUser{
    param (
        [parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [System.Object[]] $VMArray
    )
    foreach($vm in $VMArray){
        if($VM.Description -match '.*(ut[A-Za-z0-9]{5}).*'){
            [PSCustomObject]@{
            "Name" = $vm.Name  
            "User" = $Matches[1]
            }
        }
    }
}  
Mark Wragg
  • 22,105
  • 7
  • 39
  • 68

1 Answers1

8

To support pipeline input you need a process block in your function:

function Get-MachinesUser{
    param (
        [parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [System.Object[]] $VMArray
    )
    Process{
        foreach($vm in $VMArray){
            if($VM.Description -match '.*(ut[A-Za-z0-9]{5}).*'){
                [PSCustomObject]@{
                "Name" = $vm.Name  
                "User" = $Matches[1]
                }
            }
        }
    }  
}

Process

This block is used to provide record-by-record processing for the function. This block might be used any number of times, depending on the input to the function. For example, if the function is the first command in the pipeline, the Process block will be used one time. If the function is not the first command in the pipeline, the Process block is used one time for every input that the function receives from the pipeline.

Source: https://ss64.com/ps/syntax-function-input.html

(Note: The quote has been slightly amended as SS64 incorrectly indicated that the process block is not executed where there is no pipeline input, whereas in fact it still executes a single time).

You are still correct to include a ForEach loop, as this means that you support the array input when it's passed through the parameter. However in order to process all the input when it is sent via the pipeline a Process { } block is needed.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Mark Wragg
  • 22,105
  • 7
  • 39
  • 68
  • 3
    To supplement this answer, here is a [excellent guide on this topic by Boe Prox](https://learn-powershell.net/2013/05/07/tips-on-implementing-pipeline-support/) – BenH May 11 '17 at 21:42
  • This is correct, but per a comment made after this was answered and edited, he's inputting an array of strings, not objects. Would also need to change `[System.Object[]] $VMArray` to `[String[]] $VMArray` and `if($VM.Description -match` to `if($VM -match` if that's accurate – scrthq May 12 '17 at 02:21
  • I might be overlooking something here, but wy would adding the process block change the behavior for piping? As when I invoke the function, using the same variable just as a normal parameter: Get-MachineUser -VMArray $PassedArray it works fine. It might be something I just overlooked in the behavior of Powershell, but I assumed that there was no difference between passing the array or just using it as a parameter. – finalbroadcast May 12 '17 at 04:21
  • 1
    There is a difference. Using it as a parameter passes in the whole collection at once. Sending it via the pipeline powershell unrolls it automatically and deals with it one item at a time via a process block (basically automatically doing the work of the foreach). – Mark Wragg May 12 '17 at 05:04
  • 1
    To be complete, it is recommend, not only to write a "Process"-Part, you should write a "Begin"-Part (for the initial operations, that have to run only once and used for all elements, e.g. build up a remote connection to a specific server) and an "End"-Part (To release connections, dispose objects and so on) too – Ronny Kaufmann May 12 '17 at 15:13