42

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 [email protected]

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?

5 Answers 5

46

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 [email protected]

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Okay, that sounds similar to something I'd read. So taking that a step farther: is there a good way to include the function and some additional lines of script that use the function?
I haven't tested this yet, but if you want to format the output of the function (for example), this should work: -ScriptBlock {$function:foo|format-table -auto}. Basically any ScriptBlock can be any "chunk" of valid PowerShell code, so as long as you format it properly (or use semicolons at the end of each "line"), you should be good. Earlier today I was mucking around with `measure-command {$x=[xml](get-content file.xml);$x.selectsinglenode("//thing");}, for example.
39

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 Comments

Exactly what I was looking for! Your solution in combination with $Using:Var did the trick :) Thank you very much!
It works as long as your function's name does not contain "-" like Get-Foo. I don't find the syntax.
Note that for multiline functions $fooDef = @'(function with many lines)'@ is the way to go.
8

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.

2 Comments

This is the best option. Reduces clutter and is easy. Write the script just as you want it executed and voila!
That is running s local script. Not using locally defined function.
3

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

1 Comment

genius, thank you. Did not realise that you could convert a function into a scriptblock
2

I've found yet another solution to this question, using 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]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.