How do I pass named parameters with Invoke-Command?
Asked Answered
C

5

86

I have a script that I can run remotely via Invoke-Command

Invoke-Command -ComputerName (Get-Content C:\Scripts\Servers.txt) `
               -FilePath C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1

As long as I use default parameters, it works fine. However, the script has 2 named [switch] parameters (-Debug and -Clear)

How can I pass the switched parameters via the Invoke-Command? I've tried the -ArgumentList but I'm getting errors so I must have the syntax wrong or something. Any help is greatly appreciated.

Confirmatory answered 19/11, 2010 at 14:8 Comment(0)
S
104

-ArgumentList is based on use with scriptblock commands, like:

Invoke-Command -Cn (gc Servers.txt) {param($Debug=$False, $Clear=$False) C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 } -ArgumentList $False,$True

When you call it with a -File it still passes the parameters like a dumb splatted array. I've submitted a feature request to have that added to the command (please vote that up).

So, you have two options:

If you have a script that looked like this, in a network location accessible from the remote machine (note that -Debug is implied because when I use the Parameter attribute, the script gets CmdletBinding implicitly, and thus, all of the common parameters):

param(
   [Parameter(Position=0)]
   $one
,
   [Parameter(Position=1)]
   $two
,
   [Parameter()]
   [Switch]$Clear
)

"The test is for '$one' and '$two' ... and we $(if($DebugPreference -ne 'SilentlyContinue'){"will"}else{"won't"}) run in debug mode, and we $(if($Clear){"will"}else{"won't"}) clear the logs after."

Without getting hung up on the meaning of $Clear ... if you wanted to invoke that you could use either of the following Invoke-Command syntaxes:

icm -cn (gc Servers.txt) { 
    param($one,$two,$Debug=$False,$Clear=$False)
    C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 @PSBoundParameters
} -ArgumentList "uno", "dos", $false, $true

In that one, I'm duplicating ALL the parameters I care about in the scriptblock so I can pass values. If I can hard-code them (which is what I actually did), there's no need to do that and use PSBoundParameters, I can just pass the ones I need to. In the second example below I'm going to pass the $Clear one, just to demonstrate how to pass switch parameters:

icm -cn $Env:ComputerName { 
    param([bool]$Clear)
    C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $(Test-Path $Profile)

The other option

If the script is on your local machine, and you don't want to change the parameters to be positional, or you want to specify parameters that are common parameters (so you can't control them) you will want to get the content of that script and embed it in your scriptblock:

$script = [scriptblock]::create( @"
param(`$one,`$two,`$Debug=`$False,`$Clear=`$False)
&{ $(Get-Content C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -delimiter ([char]0)) } @PSBoundParameters
"@ )

Invoke-Command -Script $script -Args "uno", "dos", $false, $true

PostScript:

If you really need to pass in a variable for the script name, what you'd do will depend on whether the variable is defined locally or remotely. In general, if you have a variable $Script or an environment variable $Env:Script with the name of a script, you can execute it with the call operator (&): &$Script or &$Env:Script

If it's an environment variable that's already defined on the remote computer, that's all there is to it. If it's a local variable, then you'll have to pass it to the remote script block:

Invoke-Command -cn $Env:ComputerName { 
    param([String]$Script, [bool]$Clear)
    & $ScriptPath "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $ScriptPath, (Test-Path $Profile)
Scrap answered 19/11, 2010 at 14:39 Comment(10)
ArgumentList is available with -FilePath also. Invoke-Command [-FilePath] <string> [[-Session] <PSSession[]>] [-AsJob] [-HideComputerName] [-JobName <string>] [-T hrottleLimit <int>] [-ArgumentList <Object[]>] [-InputObject <psobject>] [<CommonParameters>]Maine
Yes ravikanth, but it seems the ArgumentList is by splatting to the script, so you can't specify named parameters?Scrap
It's a shame that StackOverflow doesn't understand PowerShell script ... the syntax highlighting of the script names leaves much to be desired.Scrap
OK, that looks good...have to try that out. However, I have a followup question: If I use icm -FilePath it will copy the script to the remote server and then execute. If I use icm -Scriptblock it does not appear to copy the script first -- it seems to assume the script exists already on the remote server in the path specified in the scriptblock. Is this this your experience as well?Confirmatory
Nothing really gets copied to the remote machine. The script you specify gets converted in to ScriptBlock and then ScriptBlock gets passed on to the remote machine and your understanding of -ScriptBlock is correctMaine
Yeah, that's exactly right. If the script isn't on the remote machine, and that's your primary use case, I guess the best answer would be to change the parameter to be a boolean not a switch, and to specify the position :-( I don't like that ... let me add an option in my answer...Scrap
Before you posted the "other option" I played around with just a simple copy-item archiveeventlogs.ps1 to the remote servers before calling the Invoke-Command. That, of course, required a foreach statement.Confirmatory
any sample using variable not literal absolute path C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 ??? I would prefer $myScriptPs1Mirthless
Any example using a variable would get really complicated @Kiquenet, because inside Invoke-Command, variables aren't defined, which means that you have to pass in the variable (or generate a scriptblock from a string). Something like this: icm -cn $Env:ComputerName { param([String]$Script, [bool]$Clear) &$Script "uno" "dos" -Debug -Clear:$Clear } -ArgumentList $ScriptPath $(Test-Path $Profile)Scrap
The feature request link to connect has been retired. It would be a useful feature though.Atwekk
W
7

My solution to this was to write the script block dynamically with [scriptblock]:Create:

# Or build a complex local script with MARKERS here, and do substitutions
# I was sending install scripts to the remote along with MSI packages
# ...for things like Backup and AV protection etc.

$p1 = "good stuff"; $p2 = "better stuff"; $p3 = "best stuff"; $etc = "!"
$script = [scriptblock]::Create("MyScriptOnRemoteServer.ps1 $p1 $p2 $etc")
#strings get interpolated/expanded while a direct scriptblock does not

# the $parms are now expanded in the script block itself
# ...so just call it:
$result = invoke-command $computer -script $script

Passing arguments was very frustrating, trying various methods, e.g.,
-arguments, $using:p1, etc. and this just worked as desired with no problems.

Since I control the contents and variable expansion of the string which creates the [scriptblock] (or script file) this way, there is no real issue with the "invoke-command" incantation.

(It shouldn't be that hard. :) )

Wriest answered 23/5, 2016 at 20:1 Comment(1)
I agree. This is the only solution I could get working with parameters simply because there is no need to pass parameters. And not able to have the $error directly propagated. Other example: using [scriptblock]::Create("New-Item -Path '$BackupPath' -ItemType Directory -Force"), I have to force If ($result.Exists) in the caller to check if something went wrong. PSVersion=5.1Extinction
T
7

I suspect its a new feature since this post was created - pass parameters to the script block using $Using:var. Then its a simple mater to pass parameters provided the script is already on the machine or in a known network location relative to the machine

Taking the main example it would be:

icm -cn $Env:ComputerName { 
    C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -one "uno" -two "dos" -Debug -Clear $Using:Clear
}
Tarsometatarsus answered 20/4, 2017 at 9:2 Comment(2)
Don't know if it is new or not, but $Using was exactly what I was looking for. Thanks for that!Hotfoot
The $using: scope was introduced in v3 of PowerShell - see the docsMcinnis
F
4

I needed something to call scripts with named parameters. We have a policy of not using ordinal positioning of parameters and requiring the parameter name.

My approach is similar to the ones above but gets the content of the script file that you want to call and sends a parameter block containing the parameters and values.

One of the advantages of this is that you can optionally choose which parameters to send to the script file allowing for non-mandatory parameters with defaults.

Assuming there is a script called "MyScript.ps1" in the temporary path that has the following parameter block:

[CmdletBinding(PositionalBinding = $False)]
param
(
    [Parameter(Mandatory = $True)] [String] $MyNamedParameter1,
    [Parameter(Mandatory = $True)] [String] $MyNamedParameter2,
    [Parameter(Mandatory = $False)] [String] $MyNamedParameter3 = "some default value"
)

This is how I would call this script from another script:

$params = @{
    MyNamedParameter1 = $SomeValue
    MyNamedParameter2 = $SomeOtherValue
}

If ($SomeCondition)
{
    $params['MyNamedParameter3'] = $YetAnotherValue
}

$pathToScript = Join-Path -Path $env:Temp -ChildPath MyScript.ps1

$sb = [scriptblock]::create(".{$(Get-Content -Path $pathToScript -Raw)} $(&{
        $args
} @params)")
Invoke-Command -ScriptBlock $sb

I have used this in lots of scenarios and it works really well. One thing that you occasionally need to do is put quotes around the parameter value assignment block. This is always the case when there are spaces in the value.

e.g. This param block is used to call a script that copies various modules into the standard location used by PowerShell C:\Program Files\WindowsPowerShell\Modules which contains a space character.

$params = @{
        SourcePath      = "$WorkingDirectory\Modules"
        DestinationPath = "'$(Join-Path -Path $([System.Environment]::GetFolderPath('ProgramFiles')) -ChildPath 'WindowsPowershell\Modules')'"
    }

Hope this helps!

Fix answered 27/10, 2016 at 14:19 Comment(0)
A
0

This is an unfortunate situation. Positional parameters work.

# test.ps1
param($myarg1, $myarg2, $myarg3)

"myarg1 $myarg1"
"myarg2 $myarg2"
"myarg3 $myarg3"
# elevated prompt
invoke-command localhost test.ps1 -args 1,$null,3

myarg1 1
myarg2
myarg3 3

Or you can hardcode a default.

# test2.ps1
param($myarg='foo2')

dir $myarg
invoke-command localhost test2.ps1

Cannot find path 'C:\Users\js\Documents\foo2' because it does not exist.

Or copy the script there:

$s = New-PSSession localhost
copy-item test2.ps1 $home\documents -ToSession $s
icm $s { .\test2.ps1 -myarg foo3 }

Cannot find path 'C:\Users\js\Documents\foo3' because it does not exist.
Atwekk answered 22/9, 2021 at 17:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.