使用 PowerShell 的 Invoke-Command 进行远程处理时,如何包含本地定义的函数?

问题描述 投票:0回答:5

我觉得我错过了一些应该显而易见的东西,但我就是不知道如何做到这一点。

我有一个 ps1 脚本,其中定义了一个函数。它调用该函数,然后尝试远程使用它:

function foo
{
    Param([string]$x)

    Write-Output $x
}

foo "Hi!"

Invoke-Command -ScriptBlock { foo "Bye!" } -ComputerName someserver.example.com -Credential [email protected]

这个简短的示例脚本打印“Hi!”然后崩溃并显示“术语‘foo’未被识别为 cmdlet、函数、脚本文件或可操作程序的名称。”

我知道该函数未在远程服务器上定义,因为它不在 ScriptBlock 中。我可以在那里重新定义它,但我宁愿不这样做。我想定义一次该函数并在本地或远程使用它。有没有好的办法呢?

powershell powershell-2.0 powershell-remoting
5个回答
42
投票

您需要传递函数本身(而不是对

ScriptBlock
中函数的调用)。

我上周也有同样的需求,并发现这个SO讨论

所以你的代码将变成:

Invoke-Command -ScriptBlock ${function:foo} -argumentlist "Bye!" -ComputerName someserver.example.com -Credential [email protected]

请注意,通过使用此方法,您只能按位置将参数传递到函数中;您无法像在本地运行函数时那样使用命名参数。


35
投票

您可以将函数的定义作为参数传递,然后通过创建脚本块并对其进行点源来重新定义远程服务器上的函数:

$fooDef = "function foo { ${function:foo} }"

Invoke-Command -ArgumentList $fooDef -ComputerName someserver.example.com -ScriptBlock {
    Param( $fooDef )

    . ([ScriptBlock]::Create($fooDef))

    Write-Host "You can call the function as often as you like:"
    foo "Bye"
    foo "Adieu!"
}

这样就无需拥有函数的副本。如果您愿意的话,您还可以通过这种方式传递多个函数:

$allFunctionDefs = "function foo { ${function:foo} }; function bar { ${function:bar} }"

6
投票

您还可以将函数以及脚本放入文件 (foo.ps1) 中,并使用 FilePath 参数将其传递给 Invoke-Command:

Invoke-Command –ComputerName server –FilePath .\foo.ps1

文件将被复制到远程计算机并执行。


1
投票

虽然这是一个老问题,但我想添加我的解决方案。

有趣的是,函数测试中脚本块的参数列表,不采用 [scriptblock] 类型的参数,因此需要转换。

Function Write-Log 
{
    param(
        [string]$Message
    )

    Write-Host -ForegroundColor Yellow "$($env:computername): $Message"
}

Function Test
{
    $sb = {
        param(
            [String]$FunctionCall
        )

        [Scriptblock]$WriteLog = [Scriptblock]::Create($FunctionCall) 
        $WriteLog.Invoke("There goes my message...")               
    }

    # Get function stack and convert to type scriptblock 
    [scriptblock]$writelog = (Get-Item "Function:Write-Log").ScriptBlock 

    # Invoke command and pass function in scriptblock form as argument 
    Invoke-Command -ComputerName SomeHost -ScriptBlock $sb -ArgumentList $writelog
}

Test

另一种可能性是将哈希表传递到我们的脚本块,其中包含您希望在远程会话中可用的所有方法:

Function Build-FunctionStack 
{
    param([ref]$dict, [string]$FunctionName)

    ($dict.Value).Add((Get-Item "Function:${FunctionName}").Name, (Get-Item "Function:${FunctionName}").Scriptblock)
}

Function MyFunctionA 
{
    param([string]$SomeValue)

    Write-Host $SomeValue
}

Function MyFunctionB
{
    param([int]$Foo)

    Write-Host $Foo
}

$functionStack = @{}

Build-FunctionStack -dict ([ref]$functionStack) -FunctionName "MyFunctionA"
Build-FunctionStack -dict ([ref]$functionStack) -FunctionName "MyFunctionB" 

Function ExecuteSomethingRemote
{
    $sb = {
        param([Hashtable]$FunctionStack)

        ([Scriptblock]::Create($functionStack["MyFunctionA"])).Invoke("Here goes my message");
        ([Scriptblock]::Create($functionStack["MyFunctionB"])).Invoke(1234);

    }

    Invoke-Command -ComputerName SomeHost -ScriptBlock $sb -ArgumentList $functionStack
}

ExecuteSomethingRemote

0
投票

我找到了这个问题的另一个解决方案,使用

Set-Item Function:

function Foo {
  param(
    [string]$x
  )

  Write-Output $x
}

$FooDef = ${Function:Foo} # or $Function:Foo as function name doesn't contain '-'

Invoke-Command -ScriptBlock {
  Set-Item Function:Foo $Using:FooDef

  Foo "Bye!"
} -ComputerName someserver.example.com -Credential [email protected]
© www.soinside.com 2019 - 2024. All rights reserved.