2

I have a function that takes a single string array as a parameter in my PowerShell .pm1 that I want to be able to call on a remote server using a second function in my .pm1 (I do not want to rely on the server having a copy of the function). I found this Using Invoke-Command -ScriptBlock on a function with arguments but it only seems to work for 'non-arrays' or for multiple parameters (where array variable is not last)

function Hello_Worlds { param([string[]]$persons)
    foreach($person in $persons){
        write-host ("hello "+$person)
    }
}

$people = "bob","joe"
Invoke-Command -ComputerName "s1" -ScriptBlock ${function:Hello_Worlds} -ArgumentList $people
#output => "hello bob" only

Invoke-Command -ComputerName "s1" -ScriptBlock ${function:Hello_Worlds} -ArgumentList $people, ""
#output => "hello bob hello joe"

I can modify my argument list like -ArgumentList $people, "" (above) to make it work by forcing the function to see the $persons variable as a single parameter and not an array of parameters, but that seems like bad practice and I sure that I am just missing something simple.

EDIT: I was directed here ArgumentList parameter in Invoke-Command don't send all array and while it works for this exact example, it requires that I KNOW which parameters require an array. Is there a generic way to pass an any arguments that would prevent this issue? I.E. I build my argument list as an array of parameters and there could be 0 or more of them and any number of them could be arrays - or am I stuck with putting this in front of calls?

foreach($parg in $myCustomGeneratedArguments) {
   if($parg -is [array]) {$paramArgs += ,$parg} 
   else {$paramArgs += $parg} 
}
casewolf
  • 190
  • 10
  • 1
    Pass the argument like this `-ArgumentList (, $people)` – Santiago Squarzon Jan 20 '23 at 15:53
  • Same purpose to this^, use it in your assignment: `$people = ,@("bob","joe")` – Abraham Zinala Jan 20 '23 at 15:55
  • Isn't using the ``` , ``` in front the same as using it in the back? This seems as kludgy as ```,""``` or am I missing something? – casewolf Jan 20 '23 at 16:57
  • Pass the function to the remote host then you can use it inside the scriptblock without any problems. See this answer https://stackoverflow.com/a/61273544/15339544. Even tho the answer is for `ForEach-Object -Parallel` it will work the same for `Invoke-Command` – Santiago Squarzon Jan 20 '23 at 18:13

2 Answers2

1

Looking at your edit I'm afraid the linked answer doesn't lead you to the easier path, which is to not use -ArgumentList at all, instead, refer to your Hello_Worlds function and to your $people array with the $using: scope modifier:

function Hello_Worlds { param([string[]]$persons)
    foreach($person in $persons){
        write-host ("hello "+$person)
    }
}

# store the function definition locally
$func   = ${function:Hello_Worlds}.ToString()
$people = "bob","joe"

Invoke-Command -ComputerName "s1" -ScriptBlock {
    # define the function in the remote scope
    ${function:Hello_Worlds} = $using:func
    # now you can use it normally
    Hello_Worlds -persons $using:people
}
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
0

I sure that I am just missing something simple.

Unfortunately, you're not (but Santiago's helpful answer offers an approach that bypasses the problem).

The bottom line is this:

  • If you're passing two or more - individually specified - arguments to -ArgumentList, everything works as expected, such as -ArgumentList $people, "" in your example.

    • The reason is that the use of , implicitly creates an enclosing array whose elements are the now nested $people array, and the empty string ("").
  • If you're passing just one argument and that one argument happens to be an array (or similar list type) that must be passed as a whole, as a single argument, you need the -ArgumentList (, $arrayOrList) workaround, i.e. you need to explicitly create an enclosing array, using the unary form of , the array constructor operator, which signals to -ArgumentList that $arrayOrList doesn't represent individual arguments, but a single argument that happens to be array-like.
    That is, you're then passing a single-element array whose only element happens to be array-like itself.

Therefore, you invariably do need to know ahead of time if a given array represents individual arguments or is a single, array-valued argument.

You can assemble an array of all arguments ahead of time in a single array variable, whose elements may or may not be nested arrays that should be passed as a whole, but there too you need know which elements are themselves array-like and therefore require the use of , :

$people = "bob", "joe"

# Assembly the list of *all* arguments in a single array variable.
$myArguments = @(
  # Add the $people array as an element, which 
  # requires the unary form of "," *here*.
  , $people
  # Add more elements as situationally needed.
  # ...  
)

# Now you can use $myArguments as-is.
Invoke-Command -ScriptBlock $function:Hello_Worlds -ArgumentList $myArguments

If you're given a value that you know represents a single argument to pass on, it is safe to always use -ArgumentList (, $someSingleArgument), whether that single argument happens to be array-like or not.

# Assume that $mySingleArgument is a value representing a *single*
# argument, which may or may not be array-like.

Invoke-Command $function:Hello_Worlds -ArgumentList (, $mySingleArgument)
mklement0
  • 382,024
  • 64
  • 607
  • 775