PowerShell Functions
In this episode of the Learn PowerShell series we’ll examine PowerShell functions. In the last episode we covered PowerShell scripts where we began to create solutions and solve problems using PowerShell. PowerShell scripts are great and many folks spend quite a bit of time at this stage when learning PowerShell. Scripts allow you to keep things fairly simple, and can help you quickly automate tasks. As you get better and begin writing more PowerShell you’re going to need to look to creating PowerShell functions!
Video
If you prefer video format over written documentation, I discuss this topic in the following TechThoughts video:
When you should write a PowerShell function
At the end of the day a PowerShell script is a set of code that performs a task. That’s what a PowerShell function does too! So, why would you choose to write a function vs a script? We’ll dive into a few reasons below. Generally speaking though, a script is something for you. When you want to start sharing your code, or having your team-mates engage it, you’ll want to start engaging some of the capabilities that functions provide. There is no firm line. For a quick task that will be run on your workstation one time, a script might make sense. If you’re writing some PowerShell that others might use, or that you might use more than once, a function will likely serve better.
- Single purposed – a function is typically used to perform a narrow task and “do one thing, and do it great”. This makes functions highly re-usable. If you write a logging function for example, you can then use that PowerShell logging function with other functions or scripts.
- Help – functions support help based comments. This allows your users to do things like type Get-Help and figure out how to use your function.
- Parameters – support for simple and advanced parameters declarations and control allows your function to be dynamic and take various forms of user input.
- Testable – functions can be tested and mocked which greatly improves the quality of the code.
Anatomy of a PowerShell function
Here is the basic layout and order of a PowerShell function. We’ll dive into each of these sections below and explore them in detail.
# function help - (optional but strongly encouraged)
# function name
# CmdletBinding - (optional)
# parameters - (optional)
# function logic (optional Begin / Process / End)
# return - (optional)
function Verb-Noun {
[CmdletBinding()]
param (
)
begin {
}
process {
}
end {
}
}
Function Help
When you include help at the top of your function it will be displayed when Get-Help is run against your function. This is a powerful capability when others are exploring your code. Taking the time to flesh out a good Synopsis and Description combined with solid examples will help others understand what your function is for, and how to use it.
All help fields are optional and you can add or remove them to your liking. I recommend you include at least the fields below at a minimum.
<#
.SYNOPSIS
Short description
.DESCRIPTION
Long description
.EXAMPLE
C:\PS>
Example of how to use this cmdlet
.EXAMPLE
C:\PS>
Another example of how to use this cmdlet
.PARAMETER InputObject
Specifies the object to be processed. You can also pipe the objects to this command.
.OUTPUTS
Output from this cmdlet (if any)
.NOTES
General notes
.COMPONENT
The component this cmdlet belongs to
#>
CmdletBinding
[CmdletBinding()]
When you write a great function and give it a name like Get-AllFileInformation, it looks and will act like a cmdlet. But it’s an important distinction that a PowerShell function is not a cmdlet. Cmdlet’s are written and compiled in C# and your PowerShell function is written in PowerShell.
CmdletBinding is an attribute of functions that enables capabilities that makes them operate more like compiled cmdlets. Adding this to the top of your function will give your function a lot of additional capabilties such as:
- Write-Verbose – allow users to use the -Verbose switch to see what your function is doing while it’s doing it.
- ShouldProcess – if your function will make a change to a system that is high risk, you may want the user to confirm the action.
- PositionalBinding – enables your function to be run without explicitly providing each parameter name. The values can be inferred by the order they are provided to your function
parameters
param (
[Parameter(
Position = 0,
Mandatory = $false
)]
[string]
$City,
[Parameter(Position = 1)]
[ValidateSet('Metric', 'USCS')]
[string]
$Units = 'USCS',
[Parameter(Position = 2)]
[ValidateSet('ar', 'af', 'be', 'ca', 'da', 'de', 'el', 'en', 'et', 'fr', 'fa', 'hu', 'ia', 'id', 'it', 'nb', 'nl', 'pl', 'pt-br', 'ro', 'ru', 'tr', 'th', 'uk', 'vi', 'zh-cn', 'zh-tw')]
[string]
$Language = 'en',
[Parameter(Position = 3)]
[switch]
$Short
)
Parameters serve to provide the user a way of dynamically interacting with your function. You can take in various forms of input and perform a wide variety of checks and validations to ensure you are getting the right kind of information.
You can stay very basic and only declare the names of your parameters. Alternatively, you can get very specific on what is required for your parameters:
- Whether the parameter is mandatory
- The position of a parameter (the order)
- The parameter type (string/int/bool/etc)
- Setting default values
- Input validation
- Parameter sets
- Much more
This post can’t cover every possible parameter configuration. Even after years of writing PowerShell I find it difficult to memorize all of the parameter settings and syntax. Know that parameter declarations are your way of taking input into your function. You can provide a lot detail and controls to them, or just keep them simple depending on your needs. Bookmark the parameter links at the bottom of this post. Even today, I visit them frequently!
Begin Process End
These three key-words are phases that you can declare within your function.
- Begin will initialize some steps at the start
- Process will process each object as received
- End can perform cleanup.
This is especially useful if you want your your function to support processing inputs from the pipeline.
This can be difficult to grasp in theory so here is an example that demonstrates it’s use.
function Get-PipelineBeginEnd {
param (
[string]$SomeInput
)
begin {
"Begin: The input is $SomeInput"
}
process {
"The value is: $_"
}
end {
"End: The input is $SomeInput"
}
}#Get-PipelineBeginEnd
1, 2, 3 | Get-PipelineBeginEnd -SomeInput 'Test'
When you run the code above note that Begin runs once, as does End. Process is run for each item passed into it and Process has access to the current object in the pipeline.
Function Logic
The logic contained inside a PowerShell function is no different that that of a script. This is where you will write the core logic that performs the task that your function is intended for. This was covered in episodes 1-10 of this course. You can also see a full live example in the PowerShell Scripts episode.
Function return
Not all functions return something when run. If your function will return something you can optionally use return. Keep in mind that when using return, all further actions in the function will stop and the function will end.
function Get-Total {
param (
[int]$Number1,
[int]$Number2
)
$total = $Number1 + $Number2
return $total
}
Get-Total -Number1 2 -Number2 2
In the above example this simple function contains two parameters of type integer. The logic of the function adds the two numbers together and returns the integer to the user.
Your first PowerShell function
Take at look at the PowerShell function below. Note the layout adheres to what we’ve previously covered. Also note that the help section provides a great amount of detail as to the purpose and use of the function.
<#
.SYNOPSIS
Returns your public IP address.
.DESCRIPTION
Queries the ipify Public IP Address API and returns your public IP.
.EXAMPLE
Get-PublicIP
Returns the public IP.
.OUTPUTS
System.String
.NOTES
https://github.com/rdegges/ipify-api
#>
function Get-PublicIP {
$uri = 'https://api.ipify.org'
try {
$invokeRestMethodSplat = @{
Uri = $uri
ErrorAction = 'Stop'
}
$publicIP = Invoke-RestMethod @invokeRestMethodSplat
}
catch {
Write-Error $_
}
return $publicIP
}#Get-PublicIP
As the actual logic of this function is fairly simple, it might be tempting to leave it as a script. Note though that this does one specific thing, and does it well. You could now incorporate this function into other advanced logic, instead of having to reference or import a script. Other users in your environment might also start engaging this function once they become familiar with it.
Function Scope
Like most languages PowerShell has scoping rules for variables. When a variable is within a function you will no longer be able to “see” it’s value. Here is an example of this behavior:
function Get-NumberTimesTwo {
param (
[int]$Number
)
$total = $Number * 2
return $total
}#Get-NumberTimesTwo
Get-NumberTimesTwo -Number 2
The function behaves as expected and will correctly multiply a number by two. In your console though, try seeing what the value of $total is. You will not be able to. This is because $total is scoped to the function, and not your active console session.
This can be confusing for people new to PowerShell and can make writing functions more difficult. There are a couple of strategies for dealing with this.
You could write the majority of your logic in a script type format, testing variables as you go. Once you are confident the logic is working as intended, you could then wrap the logic inside a function.
Alternatively, you can use Write-Host, Write-Output, or better yet Write-Debug to see what is going on with your variables inside your function!
function Get-NumberTimesTwo {
[CmdletBinding()]
param (
[int]$Number
)
$total = $Number * 2
Write-Debug $total
return $total
}#Get-NumberTimesTwo
Get-NumberTimesTwo -Number 2 -Debug
Note that with debug switch we get access to the value inside $total to ensure our logic is doing what we intend.
Closing Example
Here is a larger closing example of two PowerShell Functions that will enable you to surf the popular website reddit using PowerShell!
Visit the GitHub gist link below to access the code and load it into your console.
https://gist.github.com/techthoughts2/cd2b720c9b291510cbd643e6ca73e05f
As you explore these functions try to answer the following questions:
- Of the two functions, which one is the primary function?
- Are these standard or advanced functions?
- Which function is going to support pipeline input?
Additional Reading
- Functions
- Parameters
- Functions Advanced
- Functions Advanced Parameters
- PowerShell Approved Verbs
- CmdletBindingAttribute
- PS1 – Should you learn PowerShell?
- Learn and use PowerShell with just three commands
- Working with the PowerShell Pipeline
- PowerShell History and Current State
- Getting setup for PowerShell Development
- Working With PowerShell Variables
- Taking Control with PowerShell Logic
- PowerShell Input & Output
- PowerShell Errors and Exceptions Handling
- PowerShell Remoting
- PowerShell Scripts
- PowerShell Functions
- Manage Cloud with PowerShell
- PowerShell Modules