Building a function

So last time we tested out errorhandling in PowerShell using try, catch and finally. This time we are building on that by introducing some more ways of handling errors and building a function.

Lets start out with a basic structure and explain the parts of it.

The basic structure of a function

$ErrorActionPreference = "Stop"
Trap {
        Write-Host "Error!" -ForegroundColor Red
        $Error [0]
        Write-host "Opening new powershell window to troubleshoot!"
        Start-Process Powershell.exe
        Pause
}
Function Example-Function {
    <#
        .SYNOPSIS
        Example function

        .DESCRIPTION
        Again, example function

        .EXAMPLE
                PS C:\> Example-Function -Dynamic "test"
                Does nothing, but it looks cool
            
        .PARAMETER Dynamic
            Example parameters
    
        .NOTES
            Author: infernux 29.05.2019
        #>

        [CmdletBinding()]
        PARAM(
        [string]$Email,
        [int]$Phone,
        [switch]$DeveloperMode
        )

        BEGIN {
                $RetrySeconds = 30
        }
        PROCESS {
                Do {
                        Try {
                                #We define our varibles
                                $Service = "DummyService"
                                $ServiceStatus = Get-Service $Service -ErrorAction SilentlyContinue
                                If($ServiceStatus.Status -eq "Running") {
                                        Stop-Service $Service
                                        Start-Sleep 5
                                        $ServiceStatus = Get-Service $Service -ErrorAction SilentlyContinue
                                        If ($ServiceStatus.Status -eq "Stopped") {
                                                $Retry = $false
                                        } Else {
                                                Retry = $true
                                        }
                                } Else {
                                        Write-Host "Service does not exist, no need to run script"
                                        $Retry = $false
                                }
                        } Catch {
                                If ($_ -match "something") {
                                        Write-Host "something"
                                        $Retry = $true
                                } Elseif ($_ -match "something_else") {
                                        Write-Host "$_"
                                        Write-Host "here I could potentially have some error handling"
                                        $Retry = $true
                                } Else {
                                        $ErrorLine = $_.InvocationInfo.ScriptLineNumber
                                        Write-Host "Error was $_"
                                        Write-Host "Error was on line $ErrorLine"
                                        $Retry = $true
                                        Pause
                                }
                        } Finally {
                                If ($Retry -eq $true) {
                                        Write-Host "retrying"
                                        $Var | Out-File var.txt
                                        Start-Sleep -Seconds $RetrySeconds
                                } Else {
                                        Write-Host "Done"
                                }
                        }
                } Until ($Retry -eq $False)
        }
}

Let’s break it down into understandable pieces.

ErrorActionPreference

ErrorActionPreference defines what you want to happen when a function throws an error. I usually have this set to stop, so I can troubleshoot and see if I can solve the problem, eventually building the problemsolving itself into my function.

Trap

When PowerShell recieves a terminating error, depending on the errorhandling and action preference it might stop running the function in the current pipeline. A trap allows to specify a list of statements or actions to run whenever a fatal terminating error occurs. In our example function, it starts another window of PowerShell for you to troubleshoot in.

Trap {
        Write-Host "Error!" -ForegroundColor Red
        $Error [0]
        Write-host "Opening new powershell window to troubleshoot!"
        Start-Process Powershell.exe
        Pause
}

We will get a little bit more into try and catch using different types later, but we can also do the same for the trap function. To only trap errors from the [System.Management.Automation.CommandNotFoundException] class, we can do something like this:

Trap [System.Management.Automation.CommandNotFoundException] {
    Write-Host "something cool"
}

Obviously this is just scratching the surface, but you can read more about traps here.

Defining the function and parameters

At the start of the function itself we define the name of our function and what parameters we want it to take. We can also set the cmdletbinding which is a special attribute class that allows us to utilize more advanced script functions in PowerShell, such as the -Confirm flag.

Function Example-Function {
        <#
        .SYNOPSIS
        Example function

        .DESCRIPTION
        Again, example function

        .EXAMPLE
                PS C:\> Example-Function -Dynamic "test"
                Does nothing, but it looks cool
            
        .PARAMETER Dynamic
            Example parameters
    
        .NOTES
            Author: infernux 29.05.2019
        #>
        [CmdletBinding()]
        PARAM (
                #SOMETHING
        )

Looking at this function it’s clear that the cmdlet-name will be Example-Function. After defining the function we specify the cmdletbinding attribute (this needs to be first) and the parameters attribute, using PARAM.

An important takeaway here is that PARAM should be the first thing in your function, always, unless you are using cmdletbinding, in which case it should be second.

But, what about .SYNPOSIS, .DESCRIPTION, .EXAMPLE, .PARAMETER and .NOTES? Well, this is mostly for information purposes and help-text. It doesn’t affect the function in any other way and as such is not needed, but welcome for the people using your functions later.

Parameters

Going into parameters and parameter sets is something to almost be reserved for it’s own post, but for more details on parameters and parametersets check out a great blogpost on that here.

For our simple use, consider the following from our script

        [CmdletBinding()]
        PARAM(
        [string]$Email,
        [int]$Phone,
        [switch]$DeveloperMode
        )

This defines three parameters, none of which is mandatory. If we wanted to make one of the parameters mandatory, we could do something like

[Parameter(Position=0,mandatory=$true)]
        [string] $aMandatoryParam

Now, for our simple needs let’s imagine we have search function that lets us search for a users email based on the phone, and vice versa. Here I’ve defined the email parameter as a string - which means that whatever I pass to it will be in the variable $email for the entire time the script runs. Likewise phone is in a integer which will hold a number in the $Phone variable. I can add error-handling to the parameters themselves (define what length and format input should be in), or I can handle it using if in the script itself.

Lastly, we have a switch. This is basically just a $true or $false. If the switch -developermode mode is set, then the variable $developermode will be set to $true.

BEGIN, PROCESS and END

Function input processing methods avaible in PowerShell, all three have different usecases.

BEGIN allows us to define optional one-time pre-processing for the function. This means that everytime the module or function is loaded, everything in the BEGIN block is used once per instance of the function in the pipeline.

PROCESS is the “doing” part of the script and is used on a record-to-record basis. This means everytime the function is called or something is looped through the function, this block runs.

END is the same as begin, just one-time post-processing for each instance of the function in the pipeline.

Do, until death does us apart

Next up is the do until we have defined. This is basically our retry-function, we define a variable called $retry that is set to $false whenever a desired state is reached. If a desired state is not reached when running through the script, the variable is set to true.

Consider our example function, where we have dummy-service that we are trying to stop:

Do {
        Try {
                #We define our varibles
                $Service = "DummyService"
                $ServiceStatus = Get-Service $Service -ErrorAction SilentlyContinue
                If($ServiceStatus.Status -eq "Running") {
                        Stop-Service $Service
                        Start-Sleep 5
                        $ServiceStatus = Get-Service $Service -ErrorAction SilentlyContinue
                        If ($ServiceStatus.Status -eq "Stopped") {
                                $Retry = $false
                        } Else {
                                $Retry = $true
                        }
                } Else {
                        Write-Host "Service does not exist, no need to run script"
                        $Retry = $false
                }
        } Catch {
                If ($_ -match "something") {
                        Write-Host "something"
                        $Retry = $true
                } Elseif ($_ -match "something_else") {
                        Write-Host "$_"
                        Write-Host "here I could potentially have some error handling"
                        $Retry = $true
                } Else {
                        $ErrorLine = $_.InvocationInfo.ScriptLineNumber
                        Write-Host "Error was $_"
                        Write-Host "Error was on line $ErrorLine"
                        $Retry = $true
                        Pause
                }
        } Finally {
                If ($Retry -eq $true) {
                        Write-Host "retrying"
                        $Var | Out-File var.txt
                        Start-Sleep -Seconds $RetrySeconds
                } Else {
                        Write-Host "Done"
                }
        }
} Until ($Retry -eq $False)

As we learned in the last post when we looked at try, catch and finally these blocks helps us try to run our code and will handle any errors we help them handle. In other words, this part will handle errors for us only as well as we can write them. If there’s something that might be timing-based, such as a service starting slower on certain older hardware, a retry might be necesarry.

In our function we see that in the finally block, if $retry is set to true, we move on and try again. Everything inside the do until is looped until we reach a state where $retry is set to false.

Keep in mind this is a “dummy” function, it does nothing as it is now. The concept of this function is simple - if the service exists, stop it. If it’s stopped when we check, good - we’re done. If there’s no service, we’re done. If the service for some reason doesn’t stop or there’s an error, we either move to our trap or the function should loop.

Disclaimer

I’m not a PowerShell guru, so there might be some errors in here. There’s also no need to get bogged down by having all of the things I’ve mentioned above in your function, it will work well with only PARAM and try catch. The moral is to write good scripts that handle unexpected situtations, that are easily readable and can be expanded on to include errorhandling and the likes. If you spot anything that’s wrong, let me know!