I found this awesome post: Using Invoke-Command -ScriptBlock on a function with arguments
I'm trying to make the function call (${function:Foo}) dynamic, as in I want to pass the function name.
I tried this:
$name = "Foo"
Invoke-Command -ScriptBlock ${function:$name}
but that fails. I also tried various escape sequences, but just can't get the function name to be dynamic.
EDIT: For clarity I am adding a small test script. Of course the desired result is to call the ExternalFunction.
Function ExternalFunction()
{
write-host "I was called externally"
}
Function InternalFunction()
{
Param ([parameter(Mandatory=$true)][string]$FunctionName)
#working: Invoke-Command -ScriptBlock ${function:ExternalFunction}
#not working: Invoke-Command -ScriptBlock ${invoke-expression $FunctionName}
if (Test-Path Function:\$FunctionName) {
#working,but how to use it in ScriptBlock?
}
}
InternalFunction -FunctionName "ExternalFunction"
Alternate solution:
function foo {'I am foo!'}
$name = 'foo'
$sb = (get-command $name -CommandType Function).ScriptBlock
invoke-command -scriptblock $sb
I am foo!
as simple as :
invoke-expression $name
or if you want to keep invoke-commande for remoting for example
Invoke-Command -ScriptBlock { invoke-expression $name}
You could try the following. It tests if the name specified is a valid function before it attempts to run it:
$myfuncnamevar = "Foo"
Invoke-Command -ScriptBlock {
param($name)
if (Test-Path Function:\$name) {
#Function exists = run it
& $name
}
} -ArgumentList $myfuncnamevar
Related
I have a scriptblock defined in a.psm1 something as follows:
[scriptblock]$Script:Sb1 = {//somescript}
I am trying to use the scriptblock defined in a.psm1 in another file b.psm1.
$return = Invoke-Command -Session "DC" -Scriptblock $Script:Sb1
I am planning to export the scriptblock from a.psm1 using function. I wanted to know if there's a better way to achieve this?
a.psm1:
[scriptblock]$Script:Sb1 = {//somescript}
function Startscriptblock{
$sb = $Script:Sb1
return $sb
}
Export-ModuleMember -Function Startscriptblock
b.psm1:
Import-Module a.psm1
$sb = Startscriptblock #get scriptblock in string form
$sb = [scriptblock]::Create($sb) #Convert it into scriptblock type
$return = Invoke-Command -Session "DC" -Scriptblock $sb
I've got the following script:
param(
[string[]]$servers
)
function Write-Info($message) {
}
function Introspect($server) {
Write-Info "about to do something on server"
// other powershell stuff that works
}
Foreach ($server in $servers) {
Invoke-Command -ComputerName $server -ScriptBlock ${function:Introspect} -ArgumentList $server -credential 'MyUser'
}
That I'm trying to invoke on a remote server like so (from powershell):
.\myscript.ps1 server1,server2,server3
The script is executing, but the issue is I get an error relating to the Write-Info function like so:
The term 'Write-Info' is not recognised as the name of a cmdlet, function, script file, or operable program
The Introspect function works fine if I embed the function inside but I guess it relates to the function not being on the remote server.
How can I solve this please?
Managed to solve this by embedding the 'missing' function inside the working one:
param(
[string[]]$servers
)
function Introspect($server) {
function Write-Info($message) {
}
Write-Info "about to do something on server"
// other powershell stuff that works
}
Foreach ($server in $servers) {
Invoke-Command -ComputerName $server -ScriptBlock ${function:Introspect} -ArgumentList $server -credential 'MyUser'
}
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?
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.
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} }"
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.
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
How is it possible to use the parameters collected in a hash table for use with ArgumentList on Invoke-Command?
$CopyParams = #{
Source = 'E:\DEPARTMENTS\CBR\SHARE\Target'
Destination = 'E:\DEPARTMENTS\CBR\SHARE\Target 2'
Structure = 'yyyy-MM-dd'
}
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock ${Function:Copy-FilesHC} -ArgumentList #CopyParams
Whatever I try, it's always complaining about the 'Source':
Cannot validate argument on parameter 'Source'. The "Test-Path $_" validation script for the argument with
value "System.Collections.Hashtable" did not return true. Determine why the validation script failed
This blog talks about a similar problem, but I can't get it to work.
The same is true for a simple Copy-Item within Invoke-Command, example:
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock {Copy-Item} -ArgumentList #CopyParams
Invoke-Command : Missing an argument for parameter 'ArgumentList'. Specify a parameter of type 'System.Obj
ect[]' and try again.
At line:11 char:89
+ ... ck {Copy-Item} -ArgumentList #CopyParams
Thank you for your help.
One-liner, to convert a remote script to accept named parameters from a hash.
Given a scriptblock which you wish to call like this:
$Options = #{
Parameter1 = "foo"
Parameter2 = "bar"
}
Invoke-Command -ComputerName REMOTESERVER -ArgumentList $Options -ScriptBlock {
param(
$Parameter1,
$Parameter2
)
#Script goes here, this is just a sample
"ComputerName: $ENV:COMPUTERNAME"
"Parameter1: $Parameter1"
"Parameter2: $Parameter2"
}
You can convert it like so
Invoke-Command -Computername REMOTESERVER -ArgumentList $Options -ScriptBlock {param($Options)&{
param(
$Parameter1,
$Parameter2
)
#Script goes here, this is just a sample
"ComputerName: $ENV:COMPUTERNAME"
"Parameter1: $Parameter1"
"Parameter2: $Parameter2"
} #Options}
What's going on? Essentially we've wrapped the original script block like so:
{param($Options)& <# Original script block (including {} braces)#> #options }
This makes the original script block an anonymous function, and creates the outer script block which has a parameter $Options, which does nothing but call the inner script block, passing #options to splat the hash.
Here's one way to approach passing named parameters:
function Copy-FilesHC
{
param ($Source,$Destination,$Structure)
"Source is $Source"
"Desintation is $Destination"
"Structure is $Structure"
}
$CopyParams = #{
Source = 'E:\DEPARTMENTS\CBR\SHARE\Target'
Destination = "'E:\DEPARTMENTS\CBR\SHARE\Target 2'" #Nested quotes required due to embedded space in value.
Structure = 'yyyy-MM-dd'
}
$SB = [scriptblock]::Create(".{${Function:Copy-FilesHC}} $(&{$args}#CopyParams)")
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock $SB
Basically, you create a new script block from your invoked script, with the parameters splatted to that from the hash table. Everything is already in the script block with the values expanded, so there's no argument list to pass.
I found a workaround, but you have to make sure that your Advanced function which is located in your module file is loaded up front in the local session. So it can be used in the remote session. I wrote a small helper function for this.
Function Add-FunctionHC {
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[String]$Name
)
Process {
Try {
$Module = (Get-Command $Name -EA Stop).ModuleName
}
Catch {
Write-Error "Add-FunctionHC: Function '$Name' doesn't exist in any module"
$Global:Error.RemoveAt('1')
Break
}
if (-not (Get-Module -Name $Module)) {
Import-Module -Name $Module
}
}
}
# Load funtion for remoting
Add-FunctionHC -Name 'Copy-FilesHC'
$CopyParams = #{
Source = 'E:\DEPARTMENTS\CBR\SHARE\Target\De file.txt'
Destination = 'E:\DEPARTMENTS\CBR\SHARE\Target 2'
}
$RemoteFunctions = "function Copy-FilesHC {${function:Copy-FilesHC}}" #';' seperated to add more
Invoke-Command -ArgumentList $RemoteFunctions -ComputerName 'SERVER' -Credential $Cred -ScriptBlock {
Param (
$RemoteFunctions
)
. ([ScriptBlock]::Create($RemoteFunctions))
$CopyParams = $using:CopyParams
Copy-FilesHC #CopyParams
}
The big advantage is that you don't need to copy your complete function in the script and it can stay in the module. So when you change something in the module to the function it will also be available in the remote session, without the need to update your script.
I recently experienced a similar problem and solved it by building the hash (or rebuilding the hash) inside the invoke by leveraging the $using variable scope (more on that here)
it looks something like this:
$Source = 'E:\DEPARTMENTS\CBR\SHARE\Target'
$Destination = 'E:\DEPARTMENTS\CBR\SHARE\Target 2'
$Structure = 'yyyy-MM-dd'
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock {
$CopyParms= #{
'Source'=$Using:Source
'Destination'=$Using:Destination
'Structure'=$Using:Structure
}
Function:Copy-FilesHC #CopyParms
}
This is what works for me:
$hash = #{
PARAM1="meaning of life"
PARAM2=42
PARAM3=$true
}
$params = foreach($x in $hash.GetEnumerator()) {"$($x.Name)=""$($x.Value)"""}
I know this is late, but I ran into the same problem and found a solution that worked for me. Assigning it to a variable within the scriptblock and then using that variable to splat didn't show any problems.
Here's an example:
$param=#{"parameter","value"}
invoke-command -asjob -session $session -ScriptBlock {$a=$args[0];cmdlet #a } -ArgumentList $param
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?
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.
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} }"
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.
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