PowerShell Splatting the Argumentlist on Invoke-Command - powershell

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

Related

Export scriptblock from one file to be used in another file

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

Call a remote script from another with multiple parameters not working

I am trying to create a script that will take input (hardcoded values for now) and call an install PS script and run it on multiple servers. I am using a PSSession and Invoke-Command(see below). The below runs, but does nothing. It doesn't seem to call the other script. Beyond getting it to actually install, I need to know if it was successful or not. I'm pretty novice at Powershell, so any hints/help/suggestions would be great. The below is wrapped in a ForEach to loop the servers with $Computer
Try
{
$session = New-PSSession -ComputerName App02 -Credential $cred
$sourceInstall = $sourceFolder + 'Install\Install.ps1'
Invoke-Command -Session $session -ScriptBlock{param($serviceName, $installFolder, $sourceFolder, $Action, $username, $password) $sourceInstall} -ArgumentList ($ServiceName, $installFolder, $sourceFolder, $Action, $username, $password)
}
Catch
{
$Filename = "Error.txt"
Write-Output "ERROR: Partial Service Deployment. See error log file(s)"
Add-Content $Filename $_.Exception.Message
}
Get-PSSession | Remove-PSSession
You can use it without $Using statement in any version of PowerShell.But pass that too as an argument.
Eg:-
Invoke-Command -ScriptBlock
param($Name)
& $Command $Name
} -ArgumentList 'Get-Process','Notepad'
But you have to pass the arguments positional when using the call operator '&'
Get-Help About_Parameters
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_parameters
Regards,
Kvprasoon

Reuse PowerShell functions in Script Block [duplicate]

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

Invoke-Command with dynamic function name

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

How do I include a locally defined function when using PowerShell's Invoke-Command for remoting?

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