11

I need to process an SVN working copy in a PS script, but I have trouble passing arguments to functions. Here's what I have:

function foo($arg1, $arg2)
{
  echo $arg1
  echo $arg2.FullName
}

echo "0: $($args[0])"
echo "1: $($args[1])"
$items = get-childitem $args[1] 
$items | foreach-object -process {foo $args[0] $_}

I want to pass $arg[0] as $arg1 to foo, and $arg[1] as $arg2. However, it doesn't work, for some reason $arg1 is always empty:

PS C:\Users\sbi> .\test.ps1 blah .\Dropbox
0: blah
1: .\Dropbox
C:\Users\sbi\Dropbox\Photos
C:\Users\sbi\Dropbox\Public
C:\Users\sbi\Dropbox\sbi
PS C:\Users\sbi>

Note: The "blah"parameter isn't passed as $arg1.

I am absolutely sure this is something hilariously simple (I only just started with doing PS and still feel very clumsy), but I have banged my head against this for more than an hour now, and I can't find anything.

sbi
  • 463
  • 2
  • 4
  • 12

3 Answers3

10

The reason that $args[0] is not returning anything in the foreach-object is that $args is an automatic variable that takes unnamed, unmatched parameters to a command and the foreach-object is a new command. There aren't any unmatched parameters for the process block, so $args[0] is null.

One thing that can help is that your scripts can have parameters, just like functions.

param ($SomeText, $SomePath)
function foo($arg1, $arg2)
{
  echo $arg1
  echo $arg2.FullName
}

echo "0: $SomeText"
echo "1: $SomePath"
$items = get-childitem $SomePath
$items | foreach-object -process {foo $SomeText $_}

As you start to want more functionality from your parameters, you might want to check out a blog post I wrote on the progression of parameters from $args to the current advanced parameters we can use now.

Steven Murawski
  • 1,580
  • 3
  • 14
  • 25
  • Blah. Reasons like this are why I avoid built-ins like Foreach-Object in favor of using the good ol' raw foreach looping construct. – Trevor Sullivan Jul 06 '12 at 19:32
  • Ah, Ok. This I understand now. Damn, that seems quite counterintuitive, though! – sbi Jul 06 '12 at 19:38
3

Try something like this:

# Use an advanced function
function foo
{
  [CmdletBinding()]
  param (
      [string] $arg1
    , [string] $arg2
  )

  Write-Host -Object $arg1;
  Write-Host -Object $arg2;
}

# Create array of "args" to emulate passing them in from command line.
$args = @('blah', 'c:\test');

echo "0: $($args[0])"
echo "1: $($args[1])"

# Force items to be returned as an array, in case there's only 1 item returned
$items = @(Get-ChildItem -Path $args[1]);
Write-Host -Object "There are $($items.Count) in `$items";

# Iterate over items found in directory
foreach ($item in $items) {
    foo -Arg1 $args[0] -Arg2 $item.FullName
}
Trevor Sullivan
  • 2,063
  • 3
  • 14
  • 19
  • That "force items to be returned as an array" is something I should indeed add. Thanks for the hint! – sbi Jul 06 '12 at 19:39
  • Yeah, gotta be careful if you only get a single item back -- by casting it explicitly as an array, you get consistent results, so you can iterate over it using `foreach`. – Trevor Sullivan Jul 06 '12 at 19:43
2

The $arg[] array appears to lose scope inside the ForEach-Object.

function foo($arg1, $arg2)
{
  echo $arg1
  echo $arg2.FullName
}

echo "0: $($args[0])"
echo "1: $($args[1])"
$zero = $args[0]
$one = $args[1]
$items = get-childitem $args[1] 
$items | foreach-object {
    echo "inner 0: $($zero)"
    echo "inner 1: $($one)"
}
Justin Dearing
  • 1,037
  • 12
  • 33
  • I could accept any of the three answers I got so far, but I'll pick this one, because this shows what I ended up doing. – sbi Jul 07 '12 at 13:20