Functional Programming in PowerShell

In this post, we will take a look at the concept of functional programming and how to write a basic function in PowerShell.

8 months ago   •   5 min read

By 0xBEN
Table of contents

What is a Function?

In programming, a function is a block of code that is designed to do one task repeatedly. A function should also be idempotent.

Idempotency means that every time the function receives an input, it should process it the same every single time. That may mean the function returns a consistent output or it returns a consistent error.





Toolmaking with Functions

When you think about it, a function is just like a tool. Just like a screwdriver was designed for a specific task, a function is also a tool in programming. The function should be designed with one task in mind.





When to Create a Function?

If you find yourself repeating a task in a script, program, or terminal, this would be a great time to create a function. Let's look at an example.

Decoding a Base 64 String

To decode a base 64 string in the PowerShell terminal, you have to do two things:

  1. Use the [System.Convert] class to first decode the string back to bytes
  2. Then, cast the bytes back to regular character encoding
# Decode the base 64 string
$bytes = [System.Convert]::FromBase64String('aGVsbG8=')

# Print the bytes in the variable
$bytes

104
101
108
108
111
The base 64 string is decoded back to raw bytes
# Convert the bytes back to a string encoded in UTF8
[System.Text.Encoding]::UTF8.GetString($bytes)

hello
Convert the bytes back to an UTF-8 string

Repeating those steps multiple times would become repetitive and time-consuming. This would serve us much better as a function.





How to Create a Function in PowerShell

Create a File to Store the Code

Open your code editor of choice. I am going to use Visual Studio Code. Click on File > New File and name your code file. I'll call my file ConvertFrom-Base64.ps1. Store the file wherever you'd like – on your desktop, in a code folder, it doesn't matter for this exercise.





Function Declaration

To declare a function, first you must specify the function keyword, then the function name.

function ConvertFrom-Base64 {
\______/ \________________/
    |            |
    |            |____function name
    |
    |____function declaration         

}





Function Parameters

Most functions are going to take input from a user or another program or function. In order for a program to accept input, you should specify some parameters. Parameters are like variables in that they hold information in memory.

function ConvertFrom-Base64 {

    # Parameters go inside here
    param (
        # Parameter 1
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [String[]]$Base64String
    )

}

param() is the parameter block and where you declare any parameters the function will use. Right now, our function only accepts one parameter$Base64String.

[Parameter()] is how you define the start of a parameter.

  • The Mandatory=$true is a parameter option. We're saying that this parameter is required everytime the function is called.
  • ValueFromPipline=$true is also a parameter option and it means the function will accept piped input (I'll demonstrate this later).

[ValidateNotNullOrEmpty()] is a validator for the [String] data type. It makes sure that the $Base64String input is not empty. If it is empty, the function will fail.

[String[]] simply means that this function will accept multiple strings – or a string array.

  • If I had said [String], then the parameter would only accept one string at a time.
  • It's usually better to accept an array of inputs and loop over them – whether one or many inputs – to increase the usability of your function.





Process the Inputs

function ConvertFrom-Base64 {

    # Parameters go inside here
    param (
        # Parameter 1
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [String[]]$Base64String
    )

    # For each loop
    foreach ($string in $Base64String) {

        try {
            # Try do this code
            $bytes = [System.Convert]::FromBase64String($string)
            return [System.Text.Encoding]::UTF8.GetString($bytes)
        }
        catch {
            # Run this code in case of errors
            Write-Error $_.Exception
        }

    }

}

First, we define a foreach loop. We do this so that if the user inputs one or many base 64 strings, the function will loop over them successfully.

Next, we define a try/catch block.

  • The try block tries to run the code inside of its block first.
  • If there's an error, when running the code, the catch block will catch the error, then it will run what ever is in the catch block.
  • In this case, we are just telling PowerShell to Write-Error – or write the error to the console, but don't stop executing the function if it's in a loop. Use the throw keyword if you want the function to terminate on any errors.

The return keyword tells the function what to return back as output to the user or the program that called it.

[System.Convert] and [System.Text.Encoding] are known as .NET reflection. Because PowerShell is a Microsoft product, it was designed to have direct access to .NET libraries, namespaces, and classes.

  • A simple explanation of a library is that Microsoft have already written code that can be reused by the community.
  • In other words, if someone has already invented the wheel, no need to do the work again. We'll just use the code Microsoft have already created.





Save the Code

Now, our function is complete. Save your code in your IDE and move onto the next step.





Using the Function in the Terminal

Source in the File

Now, we are ready to source in the function to our terminal session. It's very simple. Open up PowerShell on your computer and follow these steps.

# Dot-source the file into the session

. "C:\Users\username\Desktop\ConvertFrom-Base64.ps1"
Dot-sourcing the file into our terminal session

When you dot-source a function file into your session, the function is now loaded and ready to be called or invoked.





Invoke the Function

Using the Parameter Name

ConvertFrom-Base64 -Base64String 'aGVsbG8='

hello
Pass the base 64 string to the parameter





Using the Pipeline

'aGVsbG8=' | ConvertFrom-Base64
Pass the base 64 string down the pipeline

This is possible because of the ValueFromPipeline=$true option we used on the parameter above. PowerShell knows that this parameter will accept pipeline input, so the base 64 string is passed to the $Base64String parameter for us via the pipeline.





Recursively Decoding a Base 64 String

Sometimes, on a CTF you'll have a challenge where a base 64 string has been encoded multiple times and you need to keep decoding it until you get the decoded string. Let's see how we can do that with PowerShell. Read the comments in the code for more context.

# Original base 64 string
$base64String = 'V1ZWa1YyTXlTa2hQUkRBOQ=='

# While $stop is not equal to $true
# Run the code in this block
$stop = $false
while ($stop -ne $true) {

    try {

        # Overwrite the variable
        # We know the original string was base 64 encoded multiple times
        # So, when we decode it, it will be another base 64 string
        # Eventually, it will not be a base 64 string
        # We must use -ErrorAction Stop because
        # The function uses Write-Error which is not a terminating error
        # Then, the catch block will trigger
        $base64String = $base64String | ConvertFrom-Base64 -ErrorAction Stop

    }
    catch {

        # The ConvertFrom-Base64 cmdlet will throw an error eventually
        # Then, $stop will equal $true
        # And, the while loop will exit
        $stop = $true

    }

}

As a PowerShell One-Liner

$base64String = 'V1ZWa1YyTXlTa2hQUkRBOQ==' ; $stop = $false ; while ($stop -ne $true) { try { $base64String = $base64String | ConvertFrom-Base64 -ErrorAction Stop } catch { $stop = $true } }

Spread the word

Keep reading