Home > Enterprise >  Getting an error when executing a nested ScriptBlock from Invoke-Command
Getting an error when executing a nested ScriptBlock from Invoke-Command

Time:10-28

I'm looking for method to create a wrapper around Invoke-Command that restores the current directory that I'm using on the remote machine before invoking my command. Here's what I tried to do:

    function nice_invoke {
        param(
            [string]$Computer,
            [scriptblock]$ScriptBlock
        )
    
        Set-PSDebug -Trace 0

        $cwd = (Get-Location).Path
        write-host "cmd: $cwd"
    
        $wrapper = {
            $target = $using:cwd
            if (-not (Test-Path "$target")) {
                write-host "ERROR: Directory doesn't exist on remote"
                exit 1
            }
            else {
                Set-Location $target
            }
            $sb = $using:ScriptBlock
    
            $sb.Invoke() | out-host
       }
    
       # Execute Command on remote computer in Same Directory as Local Machine
       Invoke-Command -Computer pv3039 -ScriptBlock $wrapper    
    }

Command Line:

    PS> nice_invoke -Computer pv3039 -ScriptBlock {get-location |out-host; get-ChildItem | out-host }

Error Message:

Method invocation failed because [System.String] 
does not contain a method named 'Invoke'.
  CategoryInfo          : InvalidOperation: (:) [], RuntimeException
  FullyQualifiedErrorId : MethodNotFound
  PSComputerName        : pv3039

CodePudding user response:

You can't pass a ScriptBlock like this with the $using: scope, it will get rendered to a string-literal first. Use the [ScriptBlock]::Create(string) method instead within your $wrapper block to create a ScriptBlock from a String:

$sb = [ScriptBlock]::Create($using:ScriptBlock)
$sb.Invoke() | Out-Host

Alternatively, you could also use Invoke-Command -ArgumentList $ScriptBlock, but you still have the same issue with the ScriptBlock getting rendered as a string. Nonetheless, here is an example for this case as well:

# Call `Invoke-Command -ArgumentList $ScriptBlock`
# $args[0] is the first argument passed into the `Invoke-Command` block
$sb = [ScriptBlock]::Create($args[0])
$sb.Invoke() | Out-Host

With either approach, $sb.Invoke() will execute for you from the nested block now. This limitation is similar to how some other types are incompatible with shipping across remote connections or will not survive serialization with Export/Import-CliXml; it is simply a limitation of the ScriptBlock type.


Worthy to note, this limitation persists whether using Invoke-Command or another cmdlet that initiates execution via a child PowerShell session such as Start-Job. So the solution will be the same either way.

CodePudding user response:

function nice_invoke {
    param(
        [string]$Computer,
        [scriptblock]$ScriptBlock
    )
    Set-PSDebug -Trace 0    
    $cwd = (Get-Location).Path
    write-host "cmd: $cwd"  
    $wrapper = {
        $target = $using:cwd
        if (-not (Test-Path "$target")) {
            write-host "ERROR: Directory doesn't exist on remote"
            exit 1
        }
        else {
            Set-Location $using:cwd
        }       
        $sb = [scriptblock]::Create($using:ScriptBlock)     
        $sb.Invoke() 
    }   
    # Execute Command on remote computer in Same Directory as Local Machine
    Invoke-Command -Computer pv3039 -ScriptBlock $wrapper 
}
nice_invoke -Computer pv3039 -ScriptBlock {
      hostname
      get-location 
      #dir
}
  • Related