Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a Function From Another Function in PowerShell

First time in PowerShell 5 and I'm having trouble calling a function that writes messages to a file from another function. The following is a simplified version of what I'm doing.

workflow test {
    function logMessage {
        param([string] $Msg)

        Write-Output $Msg
    }

    function RemoveMachineFromCollection{
        param([string]$Collection, [string]$Machine)

        # If there's an error
        LogMessage "Error Removing Machine"

        # If all is good
        LogMessage "successfully remove machine"
    }


    $Collections = DatabaseQuery1

    foreach -parallel($coll in $Collections) {
        logMessage "operating on $coll collection"

        $Machines = DatabaseQuery2

        foreach($Mach in $Machines) {
            logMessage "Removing $Mach from $coll"

            RemoveMachineFromCollection -Collection $coll -Machine $Mach
        }
    }
}

test

Here's the error it generates:

The term 'logMessage' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
    + CategoryInfo          : ObjectNotFound: (logMessage:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
    + PSComputerName        : [localhost]

I've tried moving the logMessage function around in the file and even tried Global scope.

In any other language I would be able to call logMessage from any other function. As that's the purpose of a function.

What's the "Workflow way" of reusing a block of code?

Do I need to create some logging module that gets loaded into the Workflow?

like image 788
Malcont3nt Avatar asked Jan 20 '17 19:01

Malcont3nt


People also ask

How do you call a function in PowerShell?

A function in PowerShell is declared with the function keyword followed by the function name and then an open and closing curly brace. The code that the function will execute is contained within those curly braces.

How do I call a PowerShell script from another PowerShell script?

You don't need Start-Process. PowerShell scripts can run other scripts. Just put the command that runs the second script as a command in the first script (the same way as you would type it on the PowerShell command line).

What is $Global in PowerShell?

Global: The scope that is in effect when PowerShell starts or when you create a new session or runspace. Variables and functions that are present when PowerShell starts have been created in the global scope, such as automatic variables and preference variables.

Can you nest functions in PowerShell?

In Windows PowerShell® Workflow, you can include functions and nested workflows to reuse and organize the commands in a script workflow. This topic explains the rules for creating and calling nested functions and workflows.


2 Answers

You could move the functions and function call to an InlineScript (PowerShell ScriptBlock) inside the workflow like below.

workflow test {
    InlineScript
    {
        function func1{
            Write-Output "Func 1"
            logMessage
        }

        function logMessage{
            Write-Output "logMessage"
        }
        func1
    }
}

Would Output:

Func 1
logMessage

As @JeffZeitlin mentioned in his answer, workflows are not PowerShell and are much more restrictive. The InlineScript block allows for normal PowerShell code to be interpreted however the scope will be tied to the InlineScript block. For instance, if you define the functions in the script block then attempt to call the func1 function outside of the InlineScript block (but still within the workflow) it will fail because it is out of scope.

The same would happen if you define the two functions either outside of the workflow or inside of the workflow but not in an InlineScript block.

Now for an example of how you can apply this to running a foreach -parallel loop.

workflow test {
    ## workflow parameter
    param($MyList)

    ## parallel foreach loop on workflow parameter
    foreach -parallel ($Item in $MyList)
    {
        ## inlinescript
        inlinescript
        {
            ## function func1 declaration
            function func1{
                param($MyItem)
                Write-Output ('Func 1, MyItem {0}' -f $MyItem)
                logMessage $MyItem
            }

            ## function logMessage declaration
            function logMessage{
                param($MyItem)
                Write-Output ('logMessage, MyItem: {0}' -f $MyItem)
            }
            ## func1 call with $Using:Item statement
            ## $Using: prefix allows us to call items that are in the workflow scope but not in the inlinescript scope.
            func1 $Using:Item
        }
    }
}

Example call to this workflow would look like this

 PS> $MyList = 1,2,3
 PS> test $MyList
     Func 1, MyItem 3
     Func 1, MyItem 1
     Func 1, MyItem 2
     logMessage, MyItem: 3
     logMessage, MyItem: 2
     logMessage, MyItem: 1

You will notice (and as expected) the output order is random since it was run in parallel.

like image 199
jkdba Avatar answered Sep 20 '22 12:09

jkdba


Powershell requires that functions be defined before use ('lexical scope'). In your example, you are calling the logMessage function before you have defined it.

You have also structured your example as a Powershell workflow. Workflows have some restrictions that ordinary scripts do not; you need to be aware of those differences. I did this search to find some descriptions and discussions of the differences; the first "hit" provides good information. I have not (yet) found anything saying whether functions can be defined in workflows, but I would be very wary of defining functions within functions (or workflows) in the first place.

like image 36
Jeff Zeitlin Avatar answered Sep 21 '22 12:09

Jeff Zeitlin