PowerShell ScriptBlock and multiple functions

11,674

Solution 1

I know this is an old thread, but I hope it helps someone.

[CmdletBinding()]
param()
$barScriptBlock = {
    function GetBar() {
        $bar = "bar"
        $bar
    }
}
$fooScriptBlock = {
    function GetFoo() {     
        $foo = "foo"
        $bar = GetBar
        $foo
        $bar
    }
}
# Concatenate all functions into a script block; add a parameter for extra credit.
$scriptBlockString = @"
param(`$hello)
$($barScriptBlock.ToString())
$($fooScriptBlock.ToString())
`$hello
GetFoo
"@
Write-Verbose $scriptBlockString
# Convert combined string to script block.
$finalScriptBlock = [ScriptBlock]::Create($scriptBlockString)
# Run synchronously, passing runtime parameter.
$result = & $finalScriptBlock "Hola"
# (Async results would not be reiably accessed here yet.)
Write-Host $result[0]
Write-Host $result[1]
Write-Host $result[2]

Solution 2

The problem is that Invoke-Command can only see what's inside ScriptBlock, it can't see functions definded outside. If you really want to - you can run everything in one line, like this:

$result = Invoke-Command  -ComputerName localhost  -ScriptBlock { function GetBar() { $bar = "bar"; $bar }; function GetFoo() { $foo = "foo"; $bar = GetBar; $foo;  $bar }; GetFoo }

But I personally would advise you to save functions in script and call Invoke-Command with -FilePath parameter, like this:

$result = Invoke-Command  -ComputerName localhost  -FilePath "\1.ps1"

Solution 3

I recently got this working:

function GetBar() {
    $bar = "bar"
    $bar
}

function GetFoo() {     
    $foo = "foo"
    $bar = GetBar
    $foo
    $bar
}

$NewScriptBlock = [scriptblock]::Create("function GetBar() { ${function:GetBar} } function GetFoo() { ${function:GetFoo} } GetFoo")
Invoke-Command -ComputerName localhost -ScriptBlock $NewScriptBlock

Alternatively you can spare a line using:

Invoke-Command -ComputerName localhost -ScriptBlock ([scriptblock]::Create("function GetBar() { ${function:GetBar} } function GetFoo() { ${function:GetFoo} } GetFoo"))

The trick is that the Create method of the ScriptBlock class adds the code inside the functions, but not the header and final bracket. You need to add that yourself, plus the final invoke.

Share:
11,674
Knows Not Much
Author by

Knows Not Much

Updated on June 04, 2022

Comments

  • Knows Not Much
    Knows Not Much almost 2 years

    I have written the following code:

    cls
    function GetFoo() { 
        function GetBar() {
            $bar = "bar"
            $bar
        }
    
        $foo = "foo"
        $bar = GetBar
        $foo
        $bar
    }
    
    
    $cred = Get-Credential "firmwide\srabhi_adm"
    $result = Invoke-Command -Credential $cred -ComputerName localhost 
    -ScriptBlock ${function:GetFoo}
    Write-Host $result[0]
    Write-Host $result[1]
    

    It works but I don't want to define GetBar inside of GetFoo.

    Can I do something like this?

    cls
    function GetBar() {
        $bar = "bar"
        $bar
    }
    
    function GetFoo() {     
        $foo = "foo"
        $bar = GetBar
        $foo
        $bar
    }
    
    
    $cred = Get-Credential "firmwide\srabhi_adm"
    $result = Invoke-Command -Credential $cred -ComputerName localhost 
    -ScriptBlock ${function:GetFoo; function:GetBar; call GetFoo}
    Write-Host $result[0]
    Write-Host $result[1]
    

    Basically I am selectively putting the functions which I want in the ScriptBlock and then calling one of them. This way I don't have to define function inside of function and I can construct the ScriptBlock by injecting the functions which I want to be a part of that ScriptBlock.