Introduction
Like most languages, PowerShell supports the use of functions. Functions are reusable bits of code with a name wrapped around them. This lets you make multiple calls to your function name allowing you code reuse.
PowerShell actually has two types of functions, Basic and Advanced. Basic functions are a lot like the 1974 VW SuperBeetle I owned back in college. No frills but useful, gets you where you are going. Did 0 to 60 mph in under 5 minutes. Most of the time.
Advanced functions have a lot more capabilities, and are more akin to a Tesla. Lots of fancy design, can do cool things, and depending on your need might be worth the extra investment.
In this post we will focus on Basic functions, saving Advanced functions for a later post.
For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.
To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.
Basic Functions
To declare a Basic Function you begin with the keyword function
. You then follow it with the name of the function. After that are a set of parenthesis, in which you put in any values you want to pass into the function (if any) that the function can use internally. The values passed in are known as parameters.
function Get-AValue($one, $two)
{
return $one * $two
}
This function is simple. You pass in two values, which it then multiplies by each other. The return
keyword is used to return the result. So how to use it?
Well, first you need to highlight the function and use F8 in VSCode (or F5 in the IDE). This will put the function in memory so it can be used.
To call it, just use the name of the function followed by the values to pass in.
Get-AValue 33 42
Result:
1386
The 33 will go into the $one
variable, then 42 will go into the $two
variable. Note that when calling the function no commas are needed to separate the values. In addition, unlike other languages they don’t have to be placed in parenthsis.
You can also take a function and assign it to a variable which will then hold the results.
$returnValue = Get-AValue 33 42
"Returned value is $returnValue"
Result:
Returned value is 1386
You can also skip placing the result into a variable, and place the function call right into the string.
"Returned value is $(Get-AValue 33 42)"
Result:
Returned value is 1386
The $()
will force PowerShell to evaluate the expression, then string interpolation will replace it with the value before returning the final string. (For more on string interpolation, see my recent article Fun With PowerShell Strings.)
Passing Parameters by Name
PowerShell also supports passing in the parameters by name.
$returnValue = Get-AValue -one 11 -two 13
"Returned value is $returnValue"
Result:
Returned value is 1386
With this, you use the names of the variables from the function declaration. Instead of a $
though, you use a -
(dash) to show this is a parameter and not a variable, -one
and -two
.
The great thing about passing in by name is that the order doesn’t matter. You can list the parameters in any order you want.
$returnValue = Get-AValue -two 13 -one 11
"Returned value is $returnValue"
Result:
Returned value is 1386
As you can see in this example we listed the second parameter, -two
, first. Because we used names, PowerShell knew which parameter to assign which value to.
No Return
Strictly speaking, the return keyword is not required. Whenever PowerShell finds a value that isn’t otherwise consumed, that is, used by assigning that value to a variable or used in some other way, PowerShell returns that value from the function.
function Get-AValue($one, $two)
{
$one * $two
}
$returnValue = Get-AValue 33 42
"Returned value is $returnValue"
Result:
Returned value is 1386
As you can see, the result of the $one * $two
calculation isn’t otherwise used in the function it is returned as you can see in the result.
This can lead to some interesting side effects. Look at this example.
function Get-AValue($one, $two)
{
$one * $two
"Hello from Get-AValue"
}
$returnValue = Get-AValue 33 42
"Returned value is $returnValue"
Result:
Returned value is 1386 Hello from Get-AValue
In this example, there are two things that aren’t consumed. First is the calculation results, second is the string Hello from Get-AValue
.
So how to get around this? Using the return keyword has another use. When PowerShell sees return, it exits the function immediately returning the result, as in this example.
function Get-AValue($one, $two)
{
if ($one -eq 33)
{ return $one + $two }
else
{ return $one + $two }
"Hello from Get-AValue"
}
$returnValue = Get-AValue 33 42
"Returned value is $returnValue"
Result:
Returned value is 75
The function saw the value for $one
is 33, so took the first branch in the if statement, adding the two. It then exited the function immediately. The Hello from Get-AValue
is never executed.
There is one problem with this demo. A basic rule of functions is that they should have one, and only one exit point (barring any error handling). Having two return
statements violates this rule. It’s easy enough to fix though.
function Get-AValue($one, $two)
{
if ($one -eq 33)
{ $retVal = $one + $two }
else
{ $retVal = $one + $two }
return $retVal
}
$returnValue = Get-AValue 33 42
"Returned value is $returnValue"
Result:
Returned value is 75
Here I assign the result of the equations to the variable $retVal
(short for return value). At the end of the function I have a single return
statement that returns the value.
The Case for Return
I have met some PowerShell professionals who say you should never use the return
statement. I have to respectfully disagree.
First, using return
speaks of clear intent. I didn’t get to a certain spot and my code and just stopped, forgetting to complete the function. Using return
clearly says “this is the value I meant to return”.
Second, I have explicit control over my code. Using it I clearly control where my code exits. Typically the return
is the last statement in my function, but it’s not required as I may want some error handling.
Third, it improves readability. When I read through the code the return
statement clearly shows what value I intend to return.
For these reasons I use the return keyword in all my functions. The choice is of course up to you, but I wanted to make the case why I like it. You will see many code samples without the return
keyword, so I wanted you to be aware of the difference and why some people use it and others don’t.
Using Multiple Functions
It’s possible to have multiple functions in the same script, and even have those functions call each other. Let’s look at this example.
function Format-FileOutput($files)
{
# Find the max length of a file name
$maxLength = Get-MaxFileNameLength $files
Write-Host "`r`nHere is the file output`r`n"
$padding = (' ' * ($maxLength + 1))
Write-Host "File Name $($padding) Size"
Write-Host ('-' * ($maxLength + 16))
foreach($file in $files)
{
Format-FileLine $file $maxLength
}
}
function Get-MaxFileNameLength($files)
{
$maxLength = 0
foreach($file in $files)
{
if ($file.Name.Length -gt $maxLength)
{ $maxLength = $file.Name.Length }
}
return $maxLength
}
function Format-FileLine($file, $maxFileNameLength)
{
# +1 will ensure there is always at least one space between
# the file name and the size
$spaces = ' ' * ($maxFileNameLength - $file.Name.Length + 1)
$retVal = "$($file.Name)$($spaces){0,15:N0}" -f $file.Length
return $retVal
}
Let’s break this down. The first function is Format-FileOutput
. It takes one parameter, $files
, which is intended to hold a list of file objects such as one returned by the Get-ChildItem
cmdlet.
The first thing it does is call another function, Get-MaxFileNameLength
and assign the result to a variable. This function will loop over all the file objects passed in and determine the length of the longest file name. This will be used later in formatting the output.
This also highlights another feature of PowerShell, the order you declare functions is not important. In some languages, you cannot declare a function that is called, in this case Get-MaxFileNameLength
after a function it’s called from, here Format-FileOutput
. Any called functions must be listed before the function they are called from.
Again, PowerShell doesn’t care, you can declare the functions in any order you wish.
Looking at Get-MaxFileNameLength
, it sets a max length of zero, then loops over the list of file objects passed in. Each file object has a Name
property, the name of the file. But the Name
property has it’s own set of properties, one of which is Length
.
This is not the length (or size) of the file, but the length of the file name. If the file name were MyScript.ps1
, the length would be 12.
As it loops it compares the length of the file name, and if it’s greater than the max length already found it replaces it. Finally it returns that value.
Returning to Format-FileOutput
, it uses some Write-Host
statements to create a nice header to display in the output.
Note that some people say not to put Write-Host
statements inside a function, that anything like Write-Host
should be done in the main script that calls the function. Normally I might agree, however this is a simple demo so we’ll go with it.
After creating a nice header, it then loops over the list of file objects that were passed in, calling the Format-FileLine
function. It passes in two parameters, the file object from the passed in array, and the maximum file name length.
The output will be in the format of:
FileName.ps1 12,345
We want the file sizes to line up neatly, so we first calculate the number of spaces we need to put between the end of the file name and the start of our file sizes. PowerShell lets you repeat a character by using the syntax char * numberoftimes
, such as ' ' * 20
to get 20 spaces.
Here we use the length of the maximum file name, then subtract the length of the current file name, and finally add one extra space at the end.
We create a return value of the file name, plus the spaces to put at the end. We then use a string formatting command to put the file size at the end. For more on PowerShell’s string formatting syntax, see my post Fun With PowerShell String Formatting.
This formatted string is returned to the Format-FileOutput
function, where it is displayed on the screen.
So how to use this? First, highlight all three functions then use F8/F5 to get them into memory. Then, just set your file location in the terminal to a spot you want to list the files in, and call the main function.
$myfiles = Get-ChildItem
Format-FileOutput $myfiles
Result:
File Name Size
----------------------------------------------------
01 - Cmdlets.ps1 3,732
02 - Providers.ps1 1,819
03 - Variables.ps1 4,717
04 - Strings.ps1 7,970
05 - Arrays and Hashtables.ps1 8,486
06 - Logic Branching and Looping.ps1 4,315
07 - Functions.ps1 6,907
08 - Classes.ps1 6,210
09 - Examples.ps1 3,125
Company.csv 9,694
Company.json 19,479
ReadMe.md 1,115
As you can see it lists my header, then the list of files. The sizes are neatly right aligned.
You can use these same functions with other directories on your drive. I’m going to change my current folder to one that has some ISOs in it. Then I’ll run my same functions.
$myfiles = Get-ChildItem
Format-FileOutput $myfiles
Result:
File Name Size
------------------------------------------------------
2018-11-13-raspbian-stretch-full.img 5,297,405,952
2018-11-13-raspbian-stretch-full.zip 1,978,611,497
Camtasia and Snagit MVP Keys.docx 63,229
Camtasia-2018-Key.txt 71
Keys.txt 798
MediaCreationTool21H1.exe 19,463,448
Office 2016 Professional Plus Keys.txt 77
SQLServer2019-x64-ENU-Dev.iso 1,433,974,784
Win10ProMultiActivationKey.txt 31
win32diskimager-1.0.0-install.exe 12,567,188
Windows10-20H2.iso 4,899,078,144
Windows10-21H1.iso 4,556,128,256
The longer file names and sizes made the output slightly wider than the original example, but as you see the functions adapted easily to this. All without any changes.
This gives me a set of functions I can reuse over and over. And I don’t have to reuse them together, I could if I needed to have code that calls just the Get-MaxFileNameLength function.
Keep Functions Small and Focused
This brings up another rule of functions. Keep the function small and focused. Each function should do one thing and return the result. This will make it much easier to reuse your functions in other projects.
The Get-MaxFileNameLength function in the above demo is a good example. It does one thing, gets the maximum file name length, and returns it.
I could call this from my main script, but I could also write a second function similar to Format-FileOutput but perhaps it could include additional information such as the last modified date. The new function could also reference Get-MaxFileNameLength, providing good code reuse.
It also reduces the amount of code you need to write in a new function, as well as reduces what you need to test, assuming Get-MaxFileNameLength has already been tested of course.
Parameter Names versus Variable Names
I want to call out something you may not have noticed. When we called Format-FileName
we passed in:
Format-FileLine $file $maxLength
The variable $maxLength
was used for the second parameter. But look at the declaration for the function:
function Format-FileLine($file, $maxFileNameLength)
In the function, the second parameter is named $maxFileNameLength
. The point in this demo was to show the variable named being passed in does not need to match the variable used in the function declaration. PowerShell can happily take $maxLength
and copy its value into $maxFileNameLength
.
Good Function Names
You probably know by now that PowerShell uses the Verb-Noun naming method for its cmdlets. In these demos we could have used any names we wanted for the functions. function Adam()
, function John()
, or function Robert()
are all valid names. Well almost.
It is generally a best practice to use the Verb-Noun naming convention when creating your own function names. Additionally, PowerShell likes you to use an approved verb. You don’t have to, but PowerShell can display a warning if you don’t.
So what are the list of approved verbs? The easiest way is to let PowerShell tell you.
Get-Verb | Sort-Object Verb
The screen shot below shows a short sample of what PowerShell has (the list is too long to reprint here). Click on it for a larger view.
You can then select an appropriate verb from the list for your function.
Conclusion
In this post we covered the use of Basic Functions in PowerShell. You saw how to declare a function, how parameters worked, as well as the pros and cons of the return
keyword.
We then got into the use of multiple functions in a PowerShell script, and how they can call one another.
In a future post we’ll dive into the concepts of Advanced Functions.
The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.
If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.