开发者

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

开发者 https://www.devze.com 2023-01-19 13:54 出处:网络
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 fi

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!' 
}
开发者_StackOverflow社区
#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}
}


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


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


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 


With the addition of Powershell classes, you can now use methods in the place of functions.

Rough example, used static methods to make it easier to understand - more info: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_classes?view=powershell-7.2#static-attribute

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

switch ($stepChoice)
{
    1
    {
        [Step1]::Step()
    }
    2
    {
        [Step2]::Step()
    }
    3
    {
        [Step3]::Step()
    }

}

# Steps.ps1
class Step1
{
    static [void]Step()
    {
        Write-Host 'Step 1'
        [Step2]::Step()
    }
}


class Step2
{
    static [void]Step()
    {
        Write-Host 'Step 2'
        [Step3]::Step()
    }
}


class Step3
{
    static [void]Step()
    {
        Write-Host 'Step 3'
        Write-Host 'Done!'
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消