header
header Register : : Login header
header
divider
menuleft
menuright
submenu
left

[August 25th, 2008] Check the home page regarding PowerShell related news from a brand new sponsor: Idera

How can a script pipe objects to itself?
Last Post 01 Feb 2008 01:20 AM by stevehiner. 8 Replies.
Printer Friendly
Sort:
PrevPrev NextNext
You are not authorized to post a reply.
Author Messages
stevehinerUser is Offline
New Member
New Member
Posts:6

--
18 Jan 2008 08:33 AM  

I've been working on a script where it would be really nice if I could pipe objects recursively into the script.  I'm able to do it without any trouble if I hardcode the path to the script (or, presumably, if I put the script in the path).  I don't really want to limit it that way though.  If I try to pipe it to ./myscript.ps1 then it likely won't work if the pwd isn't the same as the script folder.  Also, in the off-chance that a user renames the script I'd still like it to work.

I figured out how to determine what the name of the script and the path by doing this: 

$Invocation = (Get-Variable MyInvocation -Scope 0).Value

 

$ThisScript = Join-Path (Split-Path $Invocation.MyCommand.Definition) $myInvocation.MyCommand

 

That will get me the path and script name in $ThisScript.  Unfortunately I can't pipe to it.  Specifically I can't do something like this:

get-childitem *.txt | $ThisScript

I've tried various things like wrapping $ThisScript in quotes (both single and double), parentheses, braces or calling it using invoke-item.  All attempts resulted in errors.

Any advice?  Is this possible?

I suppose some people are going to wonder why I want to do something like this.  If you're really that curious I will post with more details but it's 1:33am here and I've been at this long enough. |-)

Thanks in advance,
Steve

Steve
RichSUser is Offline
New Member
New Member
Posts:41
Avatar

--
18 Jan 2008 10:15 AM  

Have you looked at Script cmdlets in the PowerShell V2 CTP.  I think that would be a good way of doing what you want

I'd be interested in seeing more details of what you are trying to do

Richard Siddaway
Microsoft MVP - PowerShell
UK PowerShell User group Leader
www.get-psuguk.org.uk
smurawskiUser is Offline
New Member
New Member
Posts:46

--
18 Jan 2008 01:00 PM  
Perhaps you could wrap the functionality you need to call recursively in a function. You can call the same function from within that function.
Steven Murawski
Co-Host - Mind of Root (www.mindofroot.com)
Host - PowerShell Basics (powershell-basics.com)
bsonposhUser is Offline
Basic Member
Basic Member
Posts:393
Avatar

--
18 Jan 2008 01:07 PM  
Perhaps $MyInvocation.MyCommand.Path is what your looking for.

Another option for this get-childitem *.txt | $ThisScript
is
invoke-expression "get-childitem *.txt | $ThisScript"
Brandon Shell
----------------
Microsoft Powershell MVP
https://mvp.support.microsoft.com/profile/Brandon
Blog: http://www.bsonposh.com
stevehinerUser is Offline
New Member
New Member
Posts:6

--
21 Jan 2008 05:10 AM  

Hey guys, thanks for the suggestions.

Richard: I haven't looked into the v2 CTP.  I haven't checked to see if it runs along side v1 and I didn't want to risk breaking my v1 install.

Steven: Yeah, I originally planned to do it with a function.  Unfortunately as far as I can tell there isn't a way to accept pipeline input in a script without using the "being, process, end" subroutines and you can't use any other functions in that kind of script.  This is the error you get if you try: "Could not process combined Begin/Process/End clauses with command text. A script or function can either have begin/process/end clauses or command text but not both."

Brandon: That technique works when I pass in something simple like "*.mpg" but won't work if I pass in a collection like "(dir *.mpg)".  Makes sense since I doubt PowerShell can expand the $file variable into that string when it represents a collection of FileInfo objects.


Here's the core structure of my script.  Once I finish the functionality of the script I'm sure I'll post the whole thing for the few people that might be interested in it.

####### Add-Foo.ps1 #######
param($File)

begin
{
 $Invocation = (Get-Variable MyInvocation -Scope 0).Value
 $ThisScript = Join-Path (Split-Path $Invocation.MyCommand.Definition) $myInvocation.MyCommand
  if ($file) {
  switch ($file.GetType()) {
   "System.Object[]" {$file | C:\Scripts\Add-Foo.ps1}
   "System.String" {Get-ChildItem $file | C:\Scripts\Add-Foo.ps1}
  }
 }
}

process
{
  if ($_)
  {
   switch ($_.GetType()) {
  "System.IO.FileInfo" {$file = $_}
  "String" {$file = Get-ChildItem $_}
 }
 $_ | Add-Member Note* Foo Bar
 $_
  }
}
##########################

Basically I'm trying to create a script that will accept files in whatever format the user wants to provide them.  Specifically I wanted to support:
Dir *.mpg | Add-Foo.ps1
Add-Foo.ps1 *.mpg
Add-Foo.ps1 (Dir *.mpg)

When you pipe into the script it runs begin{} then runs process{} once for each object piped in.

The script above works but I'd really like to get away from hardcoding the script path.  I suppose I wouldn't mind hardcoding the name of the script if I really had to.  I considered changing the pwd to the script's path before executing the statement.  That would require me to parse any path passed in to figure out if it's relative or absolute and convert it to absolute if needed.  I guess that wouldn't be hideously hard to implement but I bet it would be fragile.

Sorry it took me so long to respond.  I wrote a very similar post to this on Friday but then bumped the back button on my mouse and lost the post.  This time I was smart enough to write the post in notepad (and disable the back button on the mouse just for good measure).

Steve
PoshoholicUser is Offline
PowerShell MVP
New Member
New Member
Posts:38
Avatar

--
30 Jan 2008 07:44 PM  

Hi Steve,

It sounds like you should be using Invoke-Expression.

Since you've already figured out how to extract the name of the script file itself from the script using MyInvocation, you should be able to use Invoke-Expression internally to invoke that string path as part of a pipeline.

i.e. Invoke-Expression "Get-ChildItem *.txt | '$ThisScript'"

Don't forget the single quotes around your variable in case there is a space in the path.

The other approach would be to change your ps1 file so that it only contains one function and then dot-source the function to get it into PowerShell.  With this approach your function would contain the begin, process and end blocks and it could call itself recursively.  And there is a way to make a script that works both as a function when dot-sourced and a script when invoked using the call operator, so that users can use the ps1 file however they like -- dot-sourcing it in their profile or calling it using the call operator.

--
Kirk Munro [MVP]
Poshoholic
http://www.poshoholic.com

stevehinerUser is Offline
New Member
New Member
Posts:6

--
31 Jan 2008 08:10 AM  

Thanks for the suggestion Kirk.  I just listened to your interview on .NET Rocks this evening.  Great show!

I tried invoke-expression and it works when the original argument was a string, however, it fails when the argument is a collection.

I like the suggestions of making it work dot sourced and as a normal script.

I recently saw something in another script online that likely gave me what I need to make it work without all the schenanigans.  It turns out that, as far as I can tell, when you pipe something into any script it gets put into a $Input variable.  That means I can go back to my original script that uses normal functions rather than the begin...process...end model.  Using $Input I should be able to handle pipes and use a normal argument for cases where the user passes a path or file as an argument.

Steve
PoshoholicUser is Offline
PowerShell MVP
New Member
New Member
Posts:38
Avatar

--
31 Jan 2008 05:30 PM  

Hi Steve,

Glad you liked the .NET Rocks show.  It was certainly fun to do.  Carl and Richard are great guys and they put on a great show.

Invoke-Expression will still work for you.  You just need to make sure that you don't evaluate your input variable, but that you do evaluate the name of the script.  Like this:

 switch ($file.GetType()) {
   "System.Object[]" { Invoke-Expression "`$file | '$script'" }
   "System.String" { Invoke-Expression "Get-ChildItem `$file | '$script'" }
  }

The backtick tells PowerShell to leave the variable be when it evaluates the string expression, so if you have a string or a collection, it remains a string or a collection.  Without it the first Invoke-Expression wouldn't work properly.

I missed the first case in your switch statement when I first looked over your script.  Sorry for the confusion.

I need to finish my blog entry about how to make a ps1 file that works both as a dot-sourced function and as an invoked script...

--
Kirk Munro [MVP]
Poshoholic
http://www.poshoholic.com

stevehinerUser is Offline
New Member
New Member
Posts:6

--
01 Feb 2008 01:20 AM  
Ah, the back tick. What a great suggestion. I knew about the back tick but it never occured to me that the variable would be in scope for the invoke-expression call. Now that I think about it it's obvious that it would still be in scope. I'm going to have to try that out just to help seat it into my brain in the future.

I was able to implement my script using the $Input varible and got it working like I want to last night. I will still probably fix up the other version so I can compare performance. The begin...process...end version has the advantage of letting me run code before and after processing.


Steve
You are not authorized to post a reply.

Active Forums 4.1
right
   
footer Sponsored by Quest Software • SAPIEN Technologies • ShellTools, LLC • Microsoft Windows Server 2008 footer
footer