theimowski
theimowski

Reputation: 447

Invoke-Command with local function in ScriptBlock doesn't work when the function has no return statement

I'm trying to get the Invoke-Command cmdlet working with a CredSsp session by passing a locally defined function.

I have two local functions:

Now when I pass those functions to Invoke-Command without -Session parameter, they both succeed. However, when specifying -Session for Invoke-Command, MyCopy fails with following exception (2. case):

Cannot bind argument to parameter 'Path' because it is null.
    + CategoryInfo          : InvalidData: (:) [Copy-Item], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.CopyItemCommand
    + PSComputerName        : localhost

Could someone please explain why result of passing MyCopyReturn is different from passing MyCopy?

function MyCopy ($from, $to) {
    Copy-Item $from $to -Force -Recurse -Verbose
}

function MyCopyReturn ($from, $to) {
    Copy-Item $from $to -Force -Recurse -Verbose
    return
}

$credential = Get-Credential
$computerName = 'localhost'
$session = New-PSSession -ComputerName $computerName -Credential $credential -Authentication Credssp
# src is a file
$src = "c:\sandbox\a\test"
# dest is a directory
$dest = "c:\sandbox\b"

# 1.
# MyCopy works fine without giving session
Invoke-Command -ScriptBlock ${function:MyCopy} -ArgumentList $src,$dest

# 2.
# MyCopy DOESN'T WORK when session is given
Invoke-Command -Session $session -ScriptBlock ${function:MyCopy} -ArgumentList $src,$dest

# 4.
# MyCopyReturn works fine without the session
Invoke-Command -ScriptBlock ${function:MyCopyReturn} -ArgumentList $src,$dest

# 3.
# MyCopyReturn works fine with session too
Invoke-Command -Session $session -ScriptBlock ${function:MyCopyReturn} -ArgumentList $src,$dest

Remove-PSSession $session

Upvotes: 2

Views: 3368

Answers (1)

user4003407
user4003407

Reputation: 22132

I make some digging with ILSpy and can say, that the problem in the bug in ScriptBlock.GetPowerShell method. It does not bind parameters properly, if them come not from param block:

function f($Arg){write "`$Arg=`"$Arg`";`$Args=`"$Args`""}
function g{param($Arg) write "`$Arg=`"$Arg`";`$Args=`"$Args`""}

$Function:f.GetPowerShell('Test1','Test2').Invoke()
# $Arg="";$Args="Test1 Test2"
$Function:g.GetPowerShell('Test1','Test2').Invoke()
# $Arg="Test1";$Args="Test2"

As you can see, $Function:f.GetPowerShell('Test1','Test2') bind both arguments to $Args automatic variable and nothing get bound to $Arg. So when you invoke this command

Invoke-Command -Session $session -ScriptBlock ${function:MyCopy} -ArgumentList $src,$dest

$from and $to does not get bound properly and cause error.

Why error does not happens when you not use -Session parameter?
Looks like Invoke-Command have to convert ScriptBlock to PowerShell to invoke command in remote session and that does not required when ScriptBlock invoked locally.

Why MyCopyReturn does not cause same error?
Not all ScripBlocks can be converted to PowerShell by GetPowerShell method (in particular, it is required that ScriptBlock must have a single statement, having another statement return violate this). If that is the case (GetPowerShell throws ScriptBlockToPowerShellNotSupportedException), than Invoke-Command use some backup scenario to convert ScriptBlock to PowerShell and that scenario does not suffer from bug of GetPowerShell method.

Upvotes: 1

Related Questions