Fun With PowerShell Basic Functions

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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s