Piping to a script

This topic contains 10 replies, has 4 voices, and was last updated by Profile photo of Nick Chard Nick Chard 1 year, 8 months ago.

  • Author
    Posts
  • #23247
    Profile photo of Nick Chard
    Nick Chard
    Participant

    Hi all,

    I am trying to write a script that will accept input from the pipeline. I have found that if I pass multiple objects through the pipeline, only the last object is passed into the script.

    For example,

    [cmdletBinding()]
    param
    (
    [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
    $variable
    )
    
    $variable

    I save this as script.ps1, then run

    get-process | .\script.ps1

    The $variable contains only the last process in the list of processes, rather than the full list of processes that I was expecting.

    Can anyone help me with why this happens?

  • #23248
    Profile photo of Don Jones
    Don Jones
    Keymaster

    When a script accepts pipeline input, it needs a PROCESS block:

    [cmdletBinding[]]
    param (
    [Parameter[Mandatory=$True,ValueFromPipeline=$True]]
    $variable
    )
    PROCESS {
    $variable
    }
    

    $variable will contain one item at a time when input is piped in, and PROCESS will repeat once per piped-in object. However, were you to run:

    .\MyScript.ps1 -Variable one,two,three

    Then PROCESS would run once, and $variable would contain three objects. So it's common to enumerate:

    [cmdletBinding[]]
    param (
    [Parameter[Mandatory=$True,ValueFromPipeline=$True]]
    $variable
    )
    PROCESS {
      foreach ($var in $variable) {
        $var
      }
    }
    

    That way $var will contain just one thing, no matter how the script is run. For lots more on this, see [i]Learn PowerShell Toolmaking in a Month of Lunches[/i]. Walks through the whole thing.

  • #23256
    Profile photo of Nick Chard
    Nick Chard
    Participant

    Hi Don

    Thanks for your reply, that was exactly what I needed and I've got my script working as excepted now. However, while running a few tests, I discovered some strange behaviour.
    I put this basic script together, that contains a function and a process block for the script.

    [CmdletBinding()]
    Param
    (
    [parameter(Mandatory=$True,ValueFromPipeline=$True)]
    $variable
    )
    
    Function MyFunction
    {
    [CmdletBinding()]
    Param
    (
    [parameter(Mandatory=$True,ValueFromPipeline=$True)]
    $variable
    )
    PROCESS
    {}
    
    }
    
    PROCESS {
    }

    Anything piped to this script produces a Get-Process error, even though Get-Process does not appear anywhere in the script.
    eg

    $object | .\MyScript.ps1

    Get-Process : Cannot evaluate parameter 'Name' because its argument is specified as a script block and there is no
    input. A script block cannot be evaluated without input.
    At C:\scripts\Untitled14.ps1:21 char:9
    + PROCESS {
    + ~
    + CategoryInfo : MetadataError: (:) [Get-Process], ParameterBindingException
    + FullyQualifiedErrorId : ScriptBlockArgumentNoInput,Microsoft.PowerShell.Commands.GetProcessCommand

    It appears that the Process block in the script is trying to run Get-Process, and if I move the { from the script Process block on to its own line, the results of Get-Process are output by the script!
    Is there any explanation for this?

  • #23258
    Profile photo of Vern Anderson
    Vern Anderson
    Participant

    Your parameter needs to be expecting like input. In other words if you are looking for names of the process then your foreach loop should process those by name.

    Don said to add a Process block but you added one with empty curly braces. in those braces you need to write a foreach loop to handle whatever objects you wanted your script to process. (not only one but 2 empty process blocks)

    Another issue is that you're making it a function. Or you have added a function block, so therefore that block of code won't even run unless you called the function by name at the bottom of your script outside the last curly brace.

    Can you describe what you want your script to do with the Process objects being piped into it. Perhaps if we knew that we'd be able to help you better.

    -VERN

  • #23260
    Profile photo of Nick Chard
    Nick Chard
    Participant

    I wasn't expecting that script to do anything. I created an empty function in an empty script – but for some reason when I pipe something to it, it runs Get-Process, even though Get-Process doesn't appear anywhere in the script. It doesn't matter what I pipe to it, the result is the same, it produces output for Get-Process. It's like a bug in Powershell

  • #23261
    Profile photo of Don Jones
    Don Jones
    Keymaster

    When you use BEGIN/PROCESS/END, you're not supposed to have code – including function declarations – outside those blocks. You're getting into a complex creature – it's going to be a bit difficult to muddle through. Might want to invest in my [i]Toolmaking[/i] book to make it a bit easier. Short answer, for this one, is to enclose your second function declaration in a BEGIN block.

  • #23301
    Profile photo of Vern Anderson
    Vern Anderson
    Participant

    Well once your variable contains something in the ISE editor that variable will persistently contain those objects until you close ISE and open a new one (there is CTRL+T but that's a more advanced topic)

    So it's possible that what you're experiencing may seem like a bug, but in fact it's because that $variable in ISE still contains value until you set it to something else.

    In your scripting if you ever run into that you may want to always set the variable to zero or $Null at the first few lines of your script.

    [code]$variable = $Null[/code]

    You will still be able to add value later but adding that line ensures that ISE won't exibit this "bug" you were seeing.

    -VERN

  • #23302
    Profile photo of Vern Anderson
    Vern Anderson
    Participant

    double posted

  • #23430
    Profile photo of Nick Chard
    Nick Chard
    Participant

    @Don, I already have your Toolmaking book. I'm working my way through it and it's really changing the way I think about my scripts. I've been applying some of the techniques to my existing scripts, which is how I ended up experimenting with functions (maybe I'm getting a bit ahead of myself). For the script that I was referring to in my original post I've created a BEGIN block that contains two functions, and then a PROCESS block that calls those functions as necessary. It's working now, and everything is organised much better than before 🙂

    @Vern, whatever I do, that empty function tries to run get-process when I pipe something to it. I rebooted my machine, created a new variable, then piped that to the script, and got the same results – a get-process error, it's very strange. It doesn't really mater though, as per Don's advice, when I use a PROCESS block, I won't put anything else outside of a BEGIN/PROCESS/END block

  • #23431
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    You've stumbled across a really weird PowerShell feature. When you type a command that doesn't exist, one of the last things PS does is try to prepend "Get-" to it, so you can type things like "Service" at a prompt and it'll wind up running "Get-Service". I don't know why they did that, but it's there.

    When your code wasn't organized properly into begin / process / end, you confused the parser into treating "Process" as a command instead of a keyword, which eventually wound up calling Get-Process through that other feature.

  • #23445
    Profile photo of Nick Chard
    Nick Chard
    Participant

    @Dave, that is a weird feature! Thanks for explaining

You must be logged in to reply to this topic.