Table of Contents

Your First PowerShell Function: The Essential Building Block
Welcome to the first article in our series on building PowerShell modules! If you’ve ever found yourself copying and pasting the same lines of PowerShell code repeatedly, or if your scripts are getting long and difficult to manage, you’re in the right place.
The journey to building robust, shareable PowerShell modules begins with a fundamental concept: the PowerShell function. Functions are the heart of any well-structured script, allowing you to encapsulate reusable logic.
Why Functions?
You might be thinking, “My scripts work just fine as they are.” And they might! But as your automation tasks grow, so does the complexity. Functions address several key challenges:
Reusability: Write code once, use it many times. No more copy-pasting code blocks.
Readability: Breaking down large scripts into smaller, named functions makes your code much easier to understand, both for you and for others. Think about future you!
Maintainability: If you need to fix a bug or add a feature, you only need to change the code in one place (the function definition), rather than every instance where that logic is used.
Organization: Functions provide a logical structure, grouping related operations together.
Think of functions as mini-programs within your script. Each one has a specific job, input, and output.
The Basic Anatomy of a PowerShell Function
At its simplest, a PowerShell function looks like this:
1function My-Function {
2 # Your code goes here
3 Write-Host "Hello from My-Function!"
4}
To use this function, you simply call its name, just like you would with any built-in PowerShell cmdlet:
1My-Function
Hello from My-Function!
Adding Parameters: Making Functions Dynamic
Most useful functions need to accept input. This is where parameters come in.
You define parameters using the param()
block:
1function Say-Hello {
2 param(
3 [string]
4 $Name # This declares a string parameter called 'Name'
5 )
6 Write-Host "Hello, $Name!"
7}
Now, you can provide input when you call the function:
1Say-Hello -Name "Alice"
Hello, Alice!
Advanced Parameter Attributes
For more powerful functions, you’ll often see [CmdletBinding()]
and
[Parameter()]
attributes. We’ll dive deeper into these in future articles, but
for now here’s a sneak peek at how they can give you control over how your
function is used and help make them behave more like native PowerShell cmdlets.
1function Get-Greeting {
2 [CmdletBinding()]
3 param(
4 # The name of the person to greet.
5 [Parameter(Mandatory, Position = 0)]
6 [string]
7 $Name,
8
9 # Specify the language for the greeting.
10 [Parameter()]
11 [ValidateSet('English', 'Spanish', 'French')]
12 [string]
13 $Language = 'English'
14 )
15
16 switch ($Language) {
17 'English' {
18 Write-Host "Hello, $Name!"
19 }
20 'Spanish' {
21 Write-Host "¡Hola, $Name!"
22 }
23 'French' {
24 Write-Host "Bonjour, $Name!"
25 }
26 default {
27 Write-Host "Hello, $Name! (Language not supported)"
28 }
29 }
30}
Default Values
We gave a default of 'English'
for the Language
parameter, meaning that if
we don’t specify it, we get the English Greeting by default.
1Get-Greeting -Name 'Charlie'
Hello, Charlie!
Mandatory Parameters
Because we marked Name
as mandatory, PowerShell will prompt us to enter a
value for it if we don’t supply one. This is true even if a default has been
defined.
This means that default values for mandatory parameters don’t make a lot of sense!
1Get-Greeting
cmdlet Get-Greeting at command pipeline position 1
Supply values for the following parameters:
Name: █
Positional Parameters
Because we specified Position = 0
, 'David'
works without the -Name
parameter being explicitly used.
Because 'David'
is the first value supplied that isn’t associated with a named
parameter, PowerShell treats it as the value for the -Name
parameter.
1Get-Greeting 'David' -Language 'Spanish'
¡Hola, David!
Parameter Validation
We decorated our Language
parameter with an attribute called
[ValidateSet(...)]
. This tells PowerShell exactly which values are valid for
this parameter. If you try to use a value not in the set, PowerShell will
throw an error.
1Get-Greeting -Name 'Eve' -Language 'Klingon'
Get-Greeting: Cannot validate argument on parameter 'Language'. The argument
"Klingon" does not belong to the set "English,Spanish,French" specified by the
ValidateSet attribute. Supply an argument that is in the set and then try the
command again.
Building TextUtils: Our First Function
Throughout this series, we’ll be building a simple module called
TextUtils
. This module will house various functions for manipulating
text.
We’ll work on our very first function which will take a string as input and output its reverse.
Naming
The very first decision we need to make is what to call this function?
PowerShell, helpfully, has a clear and very opinionated pattern for naming
functions; Verb-Noun
.
We want to Reverse
a String
, so the best name is surely Reverse-String
,
right? 😅 Not quite…
For discoverability and consistency, PowerShell defines a set of “Approved
Verbs”. We can run the Get-Verb
cmdlet to see the list of these approved
verbs. You can read more about approved verbs on the Microsoft Learn docs.
Reverse
is not an approved verb so we’re going to go instead with
ConvertTo-ReversedString
. The name is incredibly descriptive and there’s
absolutely no ambiguity about what’s going to happen when it’s
used. ConvertTo-
is used when transforming data into a different format or
representation; exactly what we’re doing.
Prototyping
Let’s start with how we reverse a string. Like with anything, there’s always many ways to achieve the same thing.
The general gist of all the methods you may see online is to:
- Treat the string as an array of characters
- Iterate through that array of characters - end first
- Join the array of characters back into a string
A first pass at this could use a [StringBuilder]
, which is a fast, memory
efficient way
to manipulate strings:
1# The string we want to reverse
2$original = "hello"
3
4# Create a new StringBuilder
5$builder = [System.Text.StringBuilder]::new()
6
7# Loop through the string backwards
8for ($i = $original.Length - 1; $i -ge 0; $i--) {
9 # Append the i-th character to the StringBuilder, suppressing output
10 [void] $builder.Append($original[$i])
11}
12
13# Build the StringBuilder into a, you guessed it, string!
14$reversed = $builder.ToString()
This would, for long strings, be pretty performant. For smaller strings, however
there’s a bit of overhead with using a [StringBuilder]
. Let’s look at a
simpler way.
1$original = "hello"
2$reversed = -join $string[($original.Length-1)..0]
Great. Don’t worry too much if the code above looks a little confusing. It’s doing exactly what we did before, just using some PowerShell magic to make it just a line or two. We’ll dive more deeply into some of those syntax tricks in future posts.
Wrapping up
We can now build our ConvertTo-ReversedString
function.
1function ConvertTo-ReversedString {
2 [CmdletBinding()]
3 param(
4 # The string to be reversed
5 [Parameter(Mandatory, Position = 0)]
6 [string]
7 $String
8 )
9 process {
10 # The core logic to reverse the string
11 -join $String[($String.Length-1)..0]
12 }
13}
Notice the process block. While not strictly necessary for simple functions, it’s good practice for functions that might process pipeline input, ensuring the code runs for each object piped to it.
Save this code into a .ps1 file called ConvertTo-ReversedString.ps1
and
run it. The function is now defined in your current PowerShell session.
You can call it like:
1ConvertTo-ReversedString -String "powershell"
llehsrewop
and
1ConvertTo-ReversedString "module"
eludom
Congratulations! You’ve just written your first PowerShell function that is reusable and accepts input. This is the foundational step towards building powerful and shareable modules.
🥡 Key Takeaways
- Functions are Reusable building blocks - Write once, use everywhere. No more copy-pasting the same code across multiple scripts
- Parameters make functions dynamic - Use
param()
blocks to accept input and make your functions flexible for different scenarios - Follow PowerShell conventions - Use approved verbs (
Get
,Set
,New
,Convert
) to make your functions consistent and discoverable - Start with
[CmdletBinding()]
- Even simple functions benefit from this attribute, preparing them for advanced features later
Functions are your gateway to organized, maintainable PowerShell code. Master them, and you’re well on your way to building professional modules.
📖 Reading List
About Functions - Microsoft’s comprehensive guide to PowerShell functions, covering everything from basic syntax to advanced features
About Functions Advanced Parameters - Deep dive into parameter attributes, validation, and making your functions behave like cmdlets
PowerShell Approved Verbs - The official list of approved verbs to use in your function names for consistency
About Scopes - Understanding how PowerShell handles variable scope, including function scope
PowerShell Best Practices and Style Guide - Community-driven style guide for writing clean, readable PowerShell code
Building PowerShell Functions - Best Practices - Part of Microsoft’s PowerShell 101 series, focusing on function best practices
In the next article, we’ll take this function and build on it, packaging it into
a .psm1 file, creating the bare bones of our TextUtils
module.