如何在使用PowerShell的Invoke-Command进行远程操作时包含本地定义的函数?

37

我感觉自己错过了某些显而易见的东西,但我就是想不出如何做到这一点。

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

function foo
{
    Param([string]$x)

    Write-Output $x
}

foo "Hi!"

Invoke-Command -ScriptBlock { foo "Bye!" } -ComputerName someserver.example.com -Credential someuser@example.com

这个简短的示例脚本会打印 "Hi!",然后崩溃并显示 "The term 'foo' is not recognized as the name of a cmdlet, function, script file, or operable program."。

我理解函数在远程服务器上未被定义,因为它不在 ScriptBlock 中。我可以重新在那里定义它,但我不想这样做。我希望只定义一次函数并在本地或远程都能使用它。有没有好的方法实现这一点?

4个回答

42
你需要传递函数本身(而不是在ScriptBlock中调用该函数)。
我上周有同样的需求,发现了这个SO讨论
所以你的代码将变为:
Invoke-Command -ScriptBlock ${function:foo} -argumentlist "Bye!" -ComputerName someserver.example.com -Credential someuser@example.com

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


好的,那听起来跟我读过的某些东西很相似。那么再进一步:有没有一个好方法可以包含这个函数以及使用该函数的一些额外脚本行? - David Hogue
我还没有测试过,但如果你想格式化函数的输出(例如),这应该可以工作:-ScriptBlock {$function:foo|format-table -auto}。基本上,任何 ScriptBlock 都可以是任何有效的 PowerShell 代码“块”,只要你正确地格式化它(或在每个“行”末尾使用分号),你就应该没问题。例如,早些时候我正在处理 measure-command {$x=[xml](get-content file.xml);$x.selectsinglenode("//thing");} - alroc

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} }"

2
正是我所需要的!您的解决方案与 $Using:Var 的结合解决了问题 :) 非常感谢! - DarkLite1
只要你的函数名称不包含“-”,例如Get-Foo,它就能正常工作。我找不到语法。 - Yann Greder
1
请注意,对于多行函数,$fooDef = @'(function with many lines)'@是正确的方式。 - ndemou

6
您也可以将函数和脚本放在一个文件(foo.ps1)中,并使用FilePath参数将其传递给Invoke-Command:
Invoke-Command –ComputerName server –FilePath .\foo.ps1

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


这是最佳选择。减少混乱并且易于操作。只需编写您想要执行的脚本,然后就可以轻松完成! - Pranav Jituri

1

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

有趣的是,在函数test中的脚本块的param列表不接受[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

天才,谢谢。我没意识到你可以将一个函数转换成脚本块。 - bigbadmouse

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