How to call function outside a scriptblock

2019-02-26 05:41发布

问题:

I have a function that I should be able to call from any place in my powershell script.

The problem is that it doesn't identify the function in a script block.

In the following example I have the function getNumberFive which should return the number 5.
I want to be able to use this function inside a scriptblock when I start a new job and also in the end of the script.

Expected result:
Write the number 15 to the file "C:\tmp\result.txt"
Write to the console: "I like the number 5"

In reality:
Write the number 10 to the file "C:\tmp\result.txt"
Write to the console: "I like the number 5"

I can workaround this issue by defining the same function inside the scriptblock but then I will duplicate the function and this is not a good programming.

Another way is to define:

$func = {
    function getNumberFive(){
        return 5
    }
}
$scriptBlock = {

    Function printSum(){
        $number = getNumberFive
        $newNumber = 10 + $number  # => $newNumber should be 15
        $newNumber >> "C:\tmp\result.txt"
      }
}
Start-Job -ScriptBlock $scriptBlock -InitializationScript $func

But in this case I won't be able to call $five = getNumberFive.

I read number of methods but I didn't understand how exactly to use them:
CALLING A POWERSHELL FUNCTION IN A START-JOB SCRIPT BLOCK WHEN IT’S DEFINED IN THE SAME SCRIPT

How to pass a named function as a parameter (scriptblock)

https://social.technet.microsoft.com/Forums/ie/en-US/485df2df-1577-4770-9db9-a9c5627dd04a/how-to-pass-a-function-to-a-scriptblock?forum=winserverpowershell

PowerShell: Pass function as a parameter

Using Invoke-Command -ScriptBlock on a function with arguments

My script:

function getNumberFive(){
    return 5
}

$scriptBlock = {

    Function printSum(){
        # $number = getNumberFive => DOESN'T WORK
        # $number = Invoke-Expression ($(get-command getNumberFive) | Select -ExpandProperty Definition) => DOESN'T WORK AS EXPECTED
        # $number = &(${function:getNumberFive}) => DOESN'T WORK AS EXPECTED
        # $number = &(Get-Item function:getNumberFive) => DOESN'T WORK AS EXPECTED
        $newNumber = 10 + $number  # => $newNumber should be 15
        $newNumber >> "C:\tmp\result.txt"
    }

    printSum
}

Start-Job -ScriptBlock $scriptBlock 

$five = getNumberFive
Write-Host "I like the number"$five

Get-Job | Wait-Job
Get-Job | Stop-Job
Get-Job | Remove-Job

回答1:

When you pass a scriptblock to start-job (or invoke-expression) the PowerShell instance that executes that scriptblock only has access to that scriptblock, anything that script block loads, and anything that already exists in the PowerShell instance.

Other parts of your script are not included. (For functions locally defined in your script to be available from other, possibly remote, instances of PowerShell the whole script – not just the scriptblock – and any dependencies would need to be accessible from the other instance.)

You could refactor the code you want in both places into a module which the script block loads as well as the job creating script.

When using jobs you are executing code in another process: like any remote operation the remote executing is a separate environment.



回答2:

You can do like this:

function getNumberFive(){
    return 5
}

$a={
Function printSum($number){
        $newNumber = 10 + $number  # => $newNumber should be 15
        $newNumber >> "D:\result.txt"
    }
}

$scriptBlock = {
param($number)
    printSum -number $number
}
$five = getNumberFive
Write-Host "I like the number"$five

Start-Job -InitializationScript $a -ScriptBlock $scriptBlock -ArgumentList $five

There is a switch name -InitializationScript in Start-job where you can keep your initialization code like a function. Wrap it in a scriptblock and simply call it from the main scriptblock.

Hope it helps.