tags:

views:

2116

answers:

3

I am trying to write a Powershell script that can get pipeline input (and is expected to do so), but trying something like

ForEach-Object {
   # do something
}

doesn't actually work when using the script from the commandline as follows:

1..20 | .\test.ps1

Is there a way?

Note: I know about functions and filters. This is not what I am looking for.

+6  A: 

You can either write a filter which is a special case of a function like so:

filter SquareIt([int]$num) { $_ * $_ }

or you can create a similar function like so:

function SquareIt([int]$num) {
  Begin {
    # Executes once before first item in pipeline is processed
  }

  Process {
    # Executes once for each pipeline object
    $_ * $_
  }

  End {
    # Executes once after last pipeline object is processed
  }
}

The above works as an interactive function definiton or if in a script can be dotted into your global session (or another script). However your example indicated you wanted a script so here it is in a script that is directly usable (no dotting required):

  --- Contents of test.ps1 ---
  param([int]$num)

  Begin {
    # Executes once before first item in pipeline is processed
  }

  Process {
    # Executes once for each pipeline object
    $_ * $_
  }

  End {
    # Executes once after last pipeline object is processed
  }

With PowerShell V2, this changes a bit with "advanced functions" which embue functions with the same parameter binding features that compiled cmdlets have. See this blog post for an example of the differences. Also note that in this advanced functions case you don't use $_ to access the pipeline object. With advanced functions, pipeline objects get bound to a parameter just like they do with a cmdlet.

Keith Hill
I know about filters, but I wanted to use the script as an element in a pipeline, not a function defined in the script. Thanks, though.
Joey
The process block is what you want. It has the advantage of not holding up the pipeline.
JasonMArcher
+6  A: 

This works and there are probably other ways to do it:

foreach ($i in $input) {
    $i
}

17:12:42 PS>1..20 | .\cmd-input.ps1
1
2
3
-- snip --
18
19
20

Search for "powershell $input variable" and you will find more information and examples.
A couple are here:
PowerShell Functions and Filters PowerShell Pro!
(see the section on "Using the PowerShell Special Variable “$input”")
"Scripts, functions, and script blocks all have access to the $input variable, which provides an enumerator over the elements in the incoming pipeline. "
or
$input gotchas « Dmitry’s PowerBlog PowerShell and beyond
"... basically $input in an enumerator which provides access to the pipeline you have."

Note: I'm assuming you mean the PS command line and not the DOS command line.

Bratch
Erm, that's why there is a Powershell tag in there. Besides, on a 64 bit system I don't even have DOS anymore. But thanks, this is exactly what I was looking for.
Joey
+5  A: 

In v2 you can also accept pipeline input (by propertyName or byValue), add parameter aliases etc:

function Get-File{
    param(  
     [Parameter(
      Position=0, 
      Mandatory=$true, 
      ValueFromPipeline=$true,
      ValueFromPipelineByPropertyName=$true)
     ]
     [Alias('FullName')]
     [String[]]$FilePath
    ) 

    process {
     write-host "file path is: $FilePath"
    }
}

# test ValueFromPipelineByPropertyName 
dir | Get-File

# test ValueFromPipeline (byValue) 

"D:\scripts\s1.txt","D:\scripts\s2.txt" | Get-File

 - or -

dir *.txt | foreach {$_.fullname} | Get-File
Shay Levy
Still a function, not a script. See the comment on http://stackoverflow.com/questions/885349/how-to-write-a-powershell-script-that-accepts-pipeline-input/885627#885627.
Joey
no biggie, save the function body in a script file and pipe to the script: dir | .\Get-File.ps1
Shay Levy