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

Powershell

Powershell Problem Overview


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?

Powershell Solutions


Solution 1 - Powershell

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

Solution 2 - Powershell

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.

Solution 3 - Powershell

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

Solution 4 - Powershell

A solution from Microsoft blog, Enclose the main code in a block and call in the end,

$MainFunction={
   $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 line executes the program
& $MainFunction

Solution 5 - Powershell

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 = "$($MyInvocation.MyCommand.path | split-path)\functions.ps1"
. $functions

then

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

}

Would work just fine

Solution 6 - Powershell

Sorry, I had to comment. CMD\Batch does allow you to declare the functions below the main method just like C#.

@ECHO OFF
::Main
CALL :Function1
CALL :Function2
EXIT /b

::Functions
:Function1
    (ECHO Hello) & (ECHO World) 
EXIT /b

:Function2
    (ECHO Foo) & (ECHO Bar) 
EXIT /b	

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDan SnellView Question on Stackoverflow
Solution 1 - PowershellJay BazuziView Answer on Stackoverflow
Solution 2 - PowershellKeith HillView Answer on Stackoverflow
Solution 3 - PowershellSimon HartcherView Answer on Stackoverflow
Solution 4 - PowershellCode Name JackView Answer on Stackoverflow
Solution 5 - PowershellTaylor BirdView Answer on Stackoverflow
Solution 6 - PowershellDavid TurnerView Answer on Stackoverflow