25 Apr
A relatively new PowerShell user came into #PowerShell on IRC.FreeNode.net this week to ask a question about scripts and the pipeline, and the conversation went so well, that I thought I’d share it with you all in case it helps clear things up for you. We’ll call him “user” since he left before I could get his permission to paste this, and I’ve cleaned up, reordered and in a couple of cases added lines to make the conversation seem a little more linear than the chat really was.
user: Can you explain the distinction between $_ and a param in a script context?
Jaykul: I sure can. And I think I can do it simply … take an example script|function … actually, we’ll just use a scriptblock
Jaykul: it can have pipeline blocks, or not (the pipeline blocks are the BEGIN{...} PROCESS{...} END{...})
user: yeah, I’m with you
Jaykul: so if you pass a value via an argument, it’s present in all three blocks, and has the same value the whole time (unless you change it in your script)
user: really
Jaykul: But when you put that script in a pipeline, your BEGIN {} block is called first, and then for EACH item in the pipeline, powershell sets $_ and calls your PROCESS block, and finally, your END{} block is called after all pipeline items have been processed
Jaykul: Here, check this out (run it so you can see)
user: that really helps, thank you
Jaykul: no problem. Incidentally … in a script-block like that, you can use the Params() statement, but I wanted to loop over $args 
user: based on that I’m left wondering what the value of parameters is.
user: If I’m writing a script, chances are that I’m going to want to use it in a pipeline
user: I guess I can use parameters, but put it in a foreach-object block
user: $arr | %{myscript $_}
Jaykul: Well, if you use the foreach-object block and pass arguments, your script doesn’t persist through the life of the pipeline, so you can’t do things like counting, or comparing to the previous object, etc.
Jaykul: But you can only pass one thing at a time via the pipeline, so you still need arguments in a script like the one I just used to paste that script to the pastebin (which is here: http://huddledmasses.org/powershell-send-paste-script/ )
user: right. So I guess the concept is that scripts grab stuff off the pipeline themselves?
Jaykul: sort-of, yeah. I mean, it’s actually PowerShell that does it, but basically, the process block grabs each item off the pipeline without needing it to be specified as an argument.
Jaykul: In v2, you can be more explicit about this, because you can write a “cmdlet” in script which lets you specify parameters which should get the pipeline value
user: okay, I totally get it now. That’s why you’d write a cmdlet instead of a script?
Jaykul: Yeah! The particularly cool thing about cmdlet parameter handling … is you can do this:
Jaykul: [Parameter(FromPipelineByPropertyValue)] [Alias(“FullName”)] $FileName
Jaykul: a parameter like that would accept a string as a parameter, but if you did gci * | scriptcmdlet instead of getting the [System.IO.FileInfo] object you would get the STRING corresponding to that object’s FullName property
user: right. sweet!
Jaykul: so you can even do tricky stuff like getting multiple properties from it (as different parameters to your script)
user: I noticed from http://powershellcentral.com/scripts/184 that you’re testing the type of the pipeline object
Jaykul: yeah
user: FIleInfo vs. string
Jaykul: because it lets me do this: ls *.ps1 | Send-Paste
Jaykul: or this: get-history -count 5 | % { $_.CommandLine } | Send-Paste
Jaykul: or even send multiple files as a single paste, like: get-content *.ps1 | Send-Paste
Jaykul: (note that in this script, that’s VERY different than ls *.ps1 | Send-Paste …. which would create a paste for each file)
user: wow, this is good stuff, thanks for the time…
Send a comment