tags:

views:

64

answers:

3

I have a script that I am utilizing functions to wrap parts of the code that allow me to move through the sections at a specified point. What I have found is that I have to have the functions listed first in the script for it to run correctly. Example:

$stepChoice = read-host 'Where would you like to start.'

switch($stepChoice)
{
    1{Step1}
    2{Step2}
    3{Step3}

}

# Steps.ps1 
function Step1 { 
    'Step 1' 
    Step2 
} 
function Step2 { 
    'Step 2' 
    Step3 
} 
function Step3 { 
    'Step 3' 
    'Done!' 
}

This give me the following error:
The term 'Step1' 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. At C:\Tools\Scripts\functiontest.ps1:7 char:12 + 1{Step1 <<<< } + CategoryInfo : ObjectNotFound: (Step1:String) [], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException

If I change the order around it works fine:

# Steps.ps1 
function Step1 { 
    'Step 1' 
    Step2 
} 
function Step2 { 
    'Step 2' 
    Step3 
} 
function Step3 { 
    'Step 3' 
    'Done!' 
}

#steps
$stepChoice = read-host 'Where would you like to start.'

switch($stepChoice)
{
    1{Step1}
    2{Step2}
    3{Step3}

}

I am guessing that it is because PS is not loading the functions. Why is this and is there a better way to lay out this code structure?

+3  A: 

I'm guessing that PowerShell, being an interpreter, just goes through a script linearly top to bottom, (after tokenizing the script) and evaluates the script along the way. If it hasn't gotten to the definition of a function yet and you're attempting to invoke it, PowerShell will error out. The simple solution here is to move the function definitions before the switch statement - as you've discovered.

Even some compiled languages behave this way - most notably C/C++ which require forward declarations to work around this issue. Other languages like C# do multiple passes over the code during compilation so that forward decls aren't required.

Keith Hill
That was what I was suspecting. Thanks much.
Dan Snell
A: 

In addition to what Keith said about the interpreter order, its also part of Powershell design. Its really meant to behave as an interface to CLR Objects and even its own cmdlets. So in powershell "scripting" you are less constructing this massively complex list of actions to take, and more putting together a collection of other, smaller pieces of logic, and defining how to interact with them.

Without getting into a quasi-religious Powershell and OOP discussion, the easiest way to accomplish what you want is to bury all your functions in a separate file (call it functions.ps1) then include that at the beginning.

So assuming everything was in functions1.ps1

do a

& ./functions.ps1

then

switch($stepChoice)
{
    1{Step1}
    2{Step2}
    3{Step3}

}

Would work just fine

Taylor
OldFart
+1  A: 

Remember that in general, what works in a script should work at the command line.

This was not true in CMD. GOTO and FOR %I IN (...) DO %%I are two examples.

In PowerShell, I can run commands at the command line until I get the result I want, then paste the history in to a script, then edit out the extraneous bits.

Also, I can take a script that isn't working correctly, paste it in to an interactive shell, and study the resulting state.

At the interactive command line, there's no way you could write this:

F
function F { "Hello, World!" }

However, when reading a script, I want to read the top-level code first, and then see more detail as I scroll down. One approach is:

function Main 
{
    # ....
}

function F
{
    # ....
}

Main
Jay Bazuzi