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