How do I include a locally defined function when u

2019-01-08 19:02发布

问题:

I feel like I'm missing something that should be obvious, but I just can't figure out how to do this.

I have a ps1 script that has a function defined in it. It calls the function and then tries using it remotely:

function foo
{
    Param([string]$x)

    Write-Output $x
}

foo "Hi!"

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

This short example script prints "Hi!" and then crashes saying "The term 'foo' is not recognized as the name of a cmdlet, function, script file, or operable program."

I understand that the function is not defined on the remote server because it is not in the ScriptBlock. I could redefine it there, but I'd rather not. I'd like to define the function once and use it either locally or remotely. Is there a good way to do this?

回答1:

You need to pass the function itself (not a call to the function in the ScriptBlock).

I had the same need just last week and found this SO discussion

So your code will become:

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

Note that by using this method, you can only pass parameters into your function positionally; you can't make use of named parameters as you could when running the function locally.



回答2:

You can pass the definition of the function as a parameter, and then redefine the function on the remote server by creating a scriptblock and then dot-sourcing it:

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

This eliminates the need to have a duplicate copy of your function. You can also pass more than one function this way, if you're so inclined:

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


回答3:

You can also put the function(s) as well as the script in a file (foo.ps1) and pass that to Invoke-Command using the FilePath parameter:

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

The file will be copied to the remote computers and executed.



回答4:

Although that's an old question I would like to add my solution.

Funny enough the param list of the scriptblock within function test, does not take an argument of type [scriptblock] and therefor needs conversion.

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

Another posibility is passing a hashtable to our scriptblock containing all the methods that you would like to have available in the remote session:

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