Why do I need to have my functions written first in my PowerShell script?

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.

Non-working 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!' 
}

Error

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*

Working example

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}

}

Why?

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?


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 
{
    F
}

function F
{
    "Hello, World!"
}

Main

Reorder your script

PowerShell is a script, not a compiled language. Therefore, it goes through the script line-by-line, top to bottom, (after tokenizing the script) and evaluates each command along the way. If it hasn't gotten to the definition of a function yet and you're already attempting to invoke that function, PowerShell will throw an error.

Therefore, in this case you must move the function definitions before the switch statement - as you've discovered.

Forward declarations

Even some compiled languages behave this way, most notably C/C++, and require forward declarations to work around this issue.

Other compiled languages like C# do multiple passes over the code during compilation so that forward declarations aren't required.


You can also source your function definitions from a separate file:

Steps-Lib.ps1

# Since this is just function definitions it is safe to source
function Step1 { 
    'Step 1' 
    Step2 
} 
function Step2 { 
    'Step 2' 
    Step3 
} 
function Step3 { 
    'Step 3' 
    'Done!' 
}

Steps.ps1

# This sources the Steps-Lib.ps1 so that the functions are available
. "./Steps-Lib.ps1"

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

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