如何使用Invoke-Command传递命名参数?

83

我有一个脚本,可以通过Invoke-Command远程运行

Invoke-Command -ComputerName (Get-Content C:\Scripts\Servers.txt) `
               -FilePath C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1
只要使用默认参数,它就可以正常工作。然而,该脚本有两个命名的[开关]参数(-Debug和-Clear)。
我如何通过Invoke-Command传递开关参数?我已经尝试了-ArgumentList,但是我得到了错误,所以我肯定语法有误或者其他什么问题。非常感谢您的帮助。
5个回答

103

-ArgumentList 是基于与 脚本块 命令一起使用的,例如:

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

当你使用 -File 调用时,它仍然像一个愚蠢的 splatted 数组传递参数。我已经提交了一个功能请求,以便将其添加到命令中(请投票支持)。
所以,你有两个选择:
如果你有一个像这样的脚本,在远程机器可以访问的网络位置上(请注意,因为我使用了 Parameter 属性,所以 -Debug 是隐含的,因此所有常见的参数都会进入脚本的 CmdletBinding):
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."

不必纠结于$Clear的含义……如果你想要调用它,可以使用以下任意一种Invoke-Command语法:

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

在这个例子中,我会复制所有我关心的参数到 scriptblock 中,以便传递值。如果可以硬编码它们(这实际上是我所做的),那么就不需要使用 PSBoundParameters,只需传递我需要的参数即可。在下面的第二个例子中,我将传递 $Clear 参数,以演示如何传递开关参数:

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

另一种选择

如果脚本在您的本地计算机上,并且您不想更改参数以使其成为位置参数,或者您想指定作为公共参数(因此您无法控制它们)的参数,则需要获取该脚本的内容并将其嵌入到您的脚本块中:

$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

后记:

如果你真的需要传递一个变量作为脚本的名称,那么你需要区分这个变量是在本地定义的还是在远程定义的。一般来说,如果你有一个叫做$Script的变量或者一个叫做$Env:Script的环境变量存储了脚本的名称,你可以通过调用运算符(&)来执行它:&$Script 或者 &$Env:Script

如果这个变量已经是远程计算机上已经定义好的环境变量,那么你只需要这样做就可以了。但如果这个变量只在本地定义,那么你需要将它传递到远程脚本块中。

Invoke-Command -cn $Env:ComputerName { 
    param([String]$Script, [bool]$Clear)
    & $ScriptPath "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $ScriptPath, (Test-Path $Profile)

ArgumentList也可与-FilePath一起使用。 Invoke-Command [-FilePath] <string> [[-Session] <PSSession[]>] [-AsJob] [-HideComputerName] [-JobName <string>] [-ThrottleLimit <int>] [-ArgumentList <Object[]>] [-InputObject <psobject>] [<CommonParameters>] - ravikanth
1
是的,Ravikanth,但似乎ArgumentList是通过展开到脚本中的,所以您无法指定命名参数? - Jaykul
9
很遗憾,StackOverflow并不太理解PowerShell脚本,对于脚本名称的语法高亮处理还有很大的提升空间。 - Jaykul
好的,看起来不错...得试一下。然而,我有一个后续问题:如果我使用icm-FilePath,它会将脚本复制到远程服务器,然后执行。如果我使用icm-Scriptblock,它似乎不会先复制脚本--它似乎假定脚本已经存在于脚本块中指定的远程服务器路径中。你也有这样的经验吗? - Sean
2
实际上没有任何东西被复制到远程机器。您指定的脚本将被转换为ScriptBlock,然后ScriptBlock将传递到远程机器,您对-ScriptBlock的理解是正确的。 - ravikanth
显示剩余5条评论

7

我猜测这是一个新功能,因为这篇文章被创建时还不存在——使用$Using:var来将参数传递到脚本块中。如果脚本已经在计算机上或相对于计算机已知的网络位置上,则很容易传递参数。

以主要示例为例,应该是这样的:

icm -cn $Env:ComputerName { 
    C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -one "uno" -two "dos" -Debug -Clear $Using:Clear
}

不知道这是否是新的,但$Using正是我正在寻找的。谢谢! - JesusIsMyDriver.dll
$using: 作用域是在 PowerShell 的 v3 版本中引入的 - 参见文档 - mklement0

7
我采用的解决方案是使用[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

传递参数非常令人沮丧,试了各种方法,例如 -arguments$using:p1 等等,但这种方式完全符合预期且没有问题。
由于我可以控制创建 [scriptblock](或脚本文件)的字符串的内容和变量扩展方式,因此使用“invoke-command”也没有实际问题。
(这应该不那么困难。 :) )

我同意。这是我能够使用参数工作的唯一解决方案,因为没有必要传递参数。而且无法直接传播$error。另一个例子:使用[scriptblock]::Create("New-Item -Path '$BackupPath' -ItemType Directory -Force"),我必须在调用者中强制执行If ($result.Exists)来检查是否出现了问题。PSVersion=5.1 - reverpie

3
我需要一个能够使用命名参数调用脚本的工具。我们有一项政策,不使用位置参数,而是要求参数名称。
我的方法类似于以上方法,但它获取您想要调用的脚本文件的内容,并发送一个包含参数和值的参数块。
其中一个优点是您可以选择性地选择要发送到脚本文件中的参数,从而允许具有默认值的非强制性参数。
假设在临时路径中有一个名为"MyScript.ps1"的脚本,该脚本具有以下参数块:
[CmdletBinding(PositionalBinding = $False)]
param
(
    [Parameter(Mandatory = $True)] [String] $MyNamedParameter1,
    [Parameter(Mandatory = $True)] [String] $MyNamedParameter2,
    [Parameter(Mandatory = $False)] [String] $MyNamedParameter3 = "some default value"
)

这是我从另一个脚本中调用此脚本的方法:
$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

我已经在许多场景中使用过这个方法,效果非常好。 有时你需要在参数值分配块周围加上引号。当值中包含空格时,这总是必须的。
例如,此参数块用于调用一个脚本,该脚本将各种模块复制到PowerShell使用的标准位置C:\Program Files\WindowsPowerShell\Modules,其中包含一个空格字符。
$params = @{
        SourcePath      = "$WorkingDirectory\Modules"
        DestinationPath = "'$(Join-Path -Path $([System.Environment]::GetFolderPath('ProgramFiles')) -ChildPath 'WindowsPowershell\Modules')'"
    }

希望这可以帮到你!

0

这是一个不幸的情况。位置参数有效。

# 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

或者你可以硬编码一个默认值。

# 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.

或者复制脚本到那里:

$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.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接