Often, administrators face situations where they run scripts written by someone else. After all, that is what a community is all about. When the admin is new to PowerShell, and when he does not understand how to run a PowerShell script, he feels a little lost. And this is normal. I often meet administrators who are unsure how to run a script.

Now, sometimes, the approach I used to take was to write the script in such a way that the moment someone calls it either from the console or using one of the integrated environments, the script would run and complete its task. When the script required parameter input, I used to go with the Read-Host cmdlet, and make the input interactive.

The better way to run a PowerShell script is to understand how the script works, and instead of creating a monolith where the administrator has no control, write little functions and empower the administrator to choose what is good for them.

But what empowerment when the base is shaky? Let me show you how a script works, and what happens when you hit F5, or select Run with PowerShell. Imagine you got the following function:

function Write-HelloWorld {
    Clear-Host
    Write-Host 'Hello, world!'
}

Or, imagine that you have a script that you got from this site or my repository. You notice that the script has function definitions with a bunch of code within, and then, the script ends. You “sort of” understand what the script does. You are happy with what you read, and you proceed to run the script on, let us say, PowerShell ISE. You open the script with PowerShell ISE, press F5.

And nothing happens.

In reality, a lot has happened. But, while PowerShell saw was the function declarations and the function block, it did not see a call for action. Yet.

When PowerShell encounters a function declaration, it reads the function and loads the function into the session. The function itself may have a hundred calls to action—tasks that the function asks PowerShell to do—but PowerShell will not do any of those until you call the function. PowerShell, as of now, knows that the function is going to ask it to do the said set of tasks, and is ready for it.

This is like handing someone a plan, or a set of instructions. You haven’t asked the someone to execute any of the pieces of the plan. In the above example, PowerShell knows that Write-HelloWorld will ask it to print ‘Hello, world!’ to the host.

If you have loaded the function on to the current PowerShell session, you would have to call the function at the terminal.

PS> Write-HelloWorld

Voila, you’ll see the message on the screen now.

In short, what you did was ask PowerShell to perform all the tasks mentioned in the function, Write-HelloWorld, that it read a moment ago.

When you run a PowerShell script, PowerShell runs those cmdlets that it sees outside of the function blocks. For example, if this were a script:

Write-Host 'My script has run.'

function Write-HelloWorld {
    Clear-Host
    Write-Host 'Hello, world!'
}

PowerShell would run the first line alone and display ‘My script has run.’ It will neither clear the host, nor display ‘Hello, world!’ on the screen. Unless, of course, you call Write-HelloWorld at the prompt.

How to run the function within the script

Instead of writing a plain Write-Host 'Hello, world!' in the script body, you should always look at wrapping all the tasks into functions. This way, yes, is more work, but you would be able to reuse the code—this work is one-time.

But, what if you wanted to make the script perform all the tasks without worrying about calling the right function? You may say, ‘Okay, this was a simple function you wrote, and the script is merely four lines. What if I am using a script of four hundred lines? Should I read every line, try to figure out what function I should call, and so on?’

This is where script-writers should pitch in. Write functions with enough documentation so that administrators know what to do.

We also have another way of achieving this: call the right function at the end of the script:

function Write-HelloWorld {
    Clear-Host
    Write-Host 'Hello, world!'
}

Write-HelloWorld

Now, all you have to do is, run the script. PowerShell will show the message on the screen. When you need some configuration within the script, and the functions run for hundreds of lines, you could use the main function. (You are not required to name the function main—it can be anything; main is a convention.) You would do this like so:

function main {
    Write-Greeting 'Hey, there!'
}

funtion Write-Greeting {
    param (
        # The message
        [Parameter(Mandatory=$false, Position=0)]
        [string]
        $Message = 'Hello, world!'
    )

    Clear-Host
    Write-Host $Message
}

main

Open this script, hit F5, and you will see ‘Hey, there!’ on the screen. If you don’t want any customisation, replace the line within main to be a plain Write-Greeting, and you will see ‘Hello, world!’ when you run the script.

Here is how it works: When you run the script, PowerShell first reads the main function. It knows that you will call Write-Greeting through the main function. It does not yet complain about not knowing what Write-Greeting is, because you haven’t yet asked it to do anything.

Next, it reads Write-Greeting. It knows that it has to clear the screen and call the Write-Host cmdlet if you ask it to run the Write-Greeting function. But, Write-Greeting will not execute unless main is, and main will not execute unless you make a call to main.

In the end, you call main; you tell PowerShell to start following the instructions in main. The function, main, asks PowerShell to perform all the tasks within Write-Greeting, and Write-Greeting asks PowerShell to clear the screen and write the message.

Let us call this a script that calls the function internally.

Calling functions outside of the script

You can get rid of the main function if you want. You can manually make calls to the function. For that, you should load the script into the session. We do this using either the F5 key, or by selecting ‘Run’ in the integrated environment. Ensure that the main function or the call to the main function are not present in the script, and run the script using F5. Nothing happens.

Now, call the function:

Write-Greeting

Or pass a parameter to it:

Write-Greeting 'Some random text.'

In the former case, you will see ‘Hello, world!’, and in the latter, ‘Some random text.’

Other methods of loading functions

Scripts contain functions, and running the script loads the function into the session. We know this from above. Running such scripts now seems straight-forward; all you have to do is, hit F5. The other GUI-based way of doing this is right-click on the script file and select ‘Run with PowerShell’. But in that case, you will see no output. The console window will flash and disappear. Goal not achieved.

Instead, open a PowerShell console window. Load the script by calling the file. Then, call the function.

PS> D:\Scripts\Greeter.ps1

If there are no spaces in the path, like above, the script will load, and calling Write-Greeting from the prompt thereafter will show the greeting. But, if the path to the script has a space or a special character, the result will not be as expected. Either you’ll see an error, or if you used quotes around the path, you would see the path itself, displayed again.

For example, if you’d placed the script in the directory, D:\PowerShell Scripts, and called the script this way:

PS> 'D:\PowerShell Scripts\Greeter.ps1'

The result would be a plain:

D:\PowerShell Scripts\Greeter.ps1

Try to call the function, and it won’t tab-complete. If you manually type the command and hit Enter, PowerShell will say:

Write-Greeting: The term 'Write-Greeting' is not recognized as the name of a cmdlet, function, script file, or operable program.

Why did this happen? Remember that if you enclose anything in quotes at the PowerShell prompt and hit Enter, PowerShell will think that you are sending it a string and asking it to display the string on the console, like echo.

There are two ways to overcome this:

Calling a function without loading it into the session

To call a function without loading it into the session, use the ampersand, like so:

PS> & 'D:\PowerShell Scripts\Greeter.ps1'

But if your script file does not contain the call(s) to the function(s) within it, nothing will happen. The functions will not get loaded into the session either. The script file will execute, and that is it.

The ampersand tells PowerShell that what is going to follow is the path to a PowerShell executable file. PowerShell will execute this script. But PowerShell will not load any of the functions, variables, or anything that you declared within the script, into the session. If your script file has the necessary calls apart from the function declarations themselves, they will get executed. For example, if you put the following in the file and call the file with an ampersand in PowerShell, your message will get displayed on the screen:

function Write-Greeting {
    Clear-Host
    Write-Host 'Hello, world!'
}

Write-Greeting

Loading the function into the session

If you would like the function loaded into the session, you would need to use the dot calling operator.

PS> . 'D:\PowerShell Scripts\Greeter.ps1'

Whether your function has the function declarations alone, or function or cmdlet calls, PowerShell will, apart from acting on the calls, will also load the functions, variables, and other parts of the function into the session, so that you can call those parts of the function from within the session window.

Let us say that you loaded the following script using the dot calling operator:

function main {
    Write-Greeting 'Hey, there!'
}

funtion Write-Greeting {
    param (
        # The message
        [Parameter(Mandatory=$false, Position=0)]
        [string]
        $Message = 'Hello, world!'
    )

    Clear-Host
    Write-Host $Message
}

main

When you call this script with the dot calling operator, PowerShell will show the message, ‘Hey, there!’. But now, you can make more calls to the same function, Write-Greeting, like so:

PS> Write-Greeting 'My new message.'

Summary

In this post, we learned how to call scripts, how function definitions and calls work in PowerShell, and how to choose the right calling operator to call scripts that you come across. In other words:

  • If you want to run a script that already has all the calls built into it, open it with either Visual Studio Code or PowerShell ISE and press the F5 button.
  • If the script does not have calls, and you would like to run the script once, call the ps1 file at the PowerShell prompt with an ampersand calling operator.
  • If you would like to call the script, load its different parts into PowerShell for reuse during the session, use the dot calling operator.
  • If the script does not have the necessary calls built into it, load the script into the session (either using F5 from within an editor, or using the dot calling operator), and call the relevant function as needed, from the script.

I hope this post clears some fog around running PowerShell scripts and sort of demystifies it for newbies.