Back to top

FREE eBook: The Most USEFUL PowerShell CmdLets and more…

Grab a copy!

How To Create A Custom PowerShell CmdLet (Step By Step)

Have you ever wondered if it is possible to write a PowerShell function that will have the same look and feel as PowerShell CmdLets delivered with the Shell by Microsoft? Well, the simple answer is YES.

Our goal in this post is to show you how to achieve it and in my humble opinion this is the most important article on this blog so please be focused and read it with the most attention.

This article is Part 3 of the PowerShell Functions and CmdLets Series:

Let us show you an easy step by step approach in writing your own PowerShell CmdLets.

How To Write A Custom PowerShell CmdLet

Here are the steps to create custom PowerShell CmdLet:

In the next section, we will see the example of PowerShell CmdLet where we will go in more detail using each step presented in this bulleted list.

How To Write A Custom PowerShell CmdLet – Example Code

In this example, I will use my own Write-ErrorLog CmdLet that is part of the Efficiency Booster PowerShell Project. This project is a library of CmdLets that helps IT experts to accomplish our every day IT tasks in a more efficient way.

If you want to follow me along please download source code here.

INFO: If you want to know more about Write-ErrorLog CmdLet please read the article How To Log PowerShell Errors And Much More where I explain PowerShell ErrorHandling, how to write errors in the external Error log text file and much more.

Just a quick summer of Write-ErrorLog CmdLet. This CmdLet write errors in an external Error Log File. The errors are caught with Try Catch block within other functions which are part of PowerShell Error Handling.

TIP: We recommend to check this external Error Log file once a week at least and assess the errors occurring in it and try to improve the code in order not to break the code again. This is a regular routine of overall code quality control that has been written.

TIP: This method of handling errors in an external Error Log text file is extremely useful if you schedule your scripts and want to get feedback when, where, and which errors occur while automated scripts run.

Here are the steps that we will follow from the bulleted list defined in the previous subheading title:

Step 1: Define Function and give it a Name

Here is the syntax that defines function, function body and gives the name to the function:

Function Write-ErrorLog {
}

If you want to know more about PowerShell Function Naming convention and PowerShell approved keywords please read this section.

We can save our script as a ps1 file.

Here is the location of Write-ErrorLog script which is part of the Utils module:
…\[My] Documents\WindowsPowerShell\Modules\02utils

Write-ErrorLog CmdLet script location

Step 2: Define CmdletBinding Attribute and optional Arguments

Inside the Function body, the first command should be the definition of CmdletBinding Attribute:

Function Write-ErrorLog {
[CmdletBinding()]
}

INFO: If you want to know more about CmdletBinding Attribute please read this topic How To Use CmdletBinding Attribute.

Step 3: Define Input Parameters

Inside the Param block, we define all the input parameters:

Function Write-ErrorLog {
[CmdletBinding()]
param (

    [Parameter(Mandatory=$false,
                HelpMessage="Error from computer.")] 
    [string]$hostname,

    [Parameter(Mandatory=$false,
                HelpMessage="Environment that failed. (Test, Production, Course, Acceptance...)")] 
    [string]$env,

    [Parameter(Mandatory=$false,
                HelpMessage="Type of server that failed. (Application, Web, Integration...)")] 
    [string]$logicalname,
    
    [Parameter(Mandatory=$false,
                HelpMessage="Error message.")] 
    [string]$errormsg,
    
    [Parameter( Mandatory=$false,
                HelpMessage="Exception.")]
    [string]$exception,
    
    [Parameter(Mandatory=$false, 
                HelpMessage="Name of the script that is failing.")]
    [string]$scriptname,
     
    [Parameter(Mandatory=$false,
                HelpMessage="Script fails at line number.")]
    [string]$failinglinenumber,

    [Parameter(Mandatory=$false,
                HelpMessage="Failing line looks like.")]
    [string]$failingline,
    
    [Parameter(Mandatory=$false,
                HelpMessage="Powershell command path.")]
    [string]$pscommandpath,    

    [Parameter(Mandatory=$false,
                HelpMessage="Position message.")]
    [string]$positionmsg, 

    [Parameter(Mandatory=$false,
                HelpMessage="Stack trace.")]
    [string]$stacktrace
)
}

For this example, we have defined all the input parameters that are used to describe the error occurred in the execution of the code that we want to log in an external Error Log text file.

INFO: If you want to understand the meaning of each input parameter and what we collect with it please read this article How To Log PowerShell Errors And Much More.

INFO: Everything you want to know about PowerShell input parameters with awesome examples has been written in How To Create Parameters In PowerShell. Here we cover Parameter Data Types, Parameter Attributes, Arguments, and Argument Values.

INFO: Sometimes you need to implement PowerShell Parameter Sets in your functions so here is a useful article about that topic How To Use Parameter Sets In PowerShell Functions.

Step 4: Define Input Methods for Pipeline

We use Begin, Process, and End blocks in order to be able to control the function’s workflow when values are passed through the PowerShell Pipeline. Each block is defined within a function body using the following syntax:

Block_Name { 

}

Step 5: Write The Code Within Each Block (Begin, Process, and End)

Please look at the implementation code for each block below.

In the BEGIN block, we test whether the Error log folder and file exist if not they will be created. This block is used for preprocessing with initialization and setup for main processing.

In the PROCESS block, we have the main functionality that will be process record-by-record. Here we write different error message properties into External Error Log text file.

In the END block, we can write postprocessing commands but in this example it is empty.

INFO: Behaviour of Begin, Process, End blocks depend on the type of call to the function: parameter, pipeline, etc. All of this has been explained in the article PowerShell Function Begin Process End Blocks Explained With Examples.

INFO: Since input methods are used in conjunction with PowerShell Pipeline which is one of the essential concepts in Windows PowerShell please read more detailed in How PowerShell Pipeline Works.

Our Function is done and here is the whole source code:

Function Write-ErrorLog {
[CmdletBinding()]
param (

    [Parameter(Mandatory=$false,
                HelpMessage="Error from computer.")] 
    [string]$hostname,

    [Parameter(Mandatory=$false,
                HelpMessage="Environment that failed. (Test, Production, Course, Acceptance...)")] 
    [string]$env,

    [Parameter(Mandatory=$false,
                HelpMessage="Type of server that failed. (Application, Web, Integration...)")] 
    [string]$logicalname,
    
    [Parameter(Mandatory=$false,
                HelpMessage="Error message.")] 
    [string]$errormsg,
    
    [Parameter( Mandatory=$false,
                HelpMessage="Exception.")]
    [string]$exception,
    
    [Parameter(Mandatory=$false, 
                HelpMessage="Name of the script that is failing.")]
    [string]$scriptname,
     
    [Parameter(Mandatory=$false,
                HelpMessage="Script fails at line number.")]
    [string]$failinglinenumber,

    [Parameter(Mandatory=$false,
                HelpMessage="Failing line looks like.")]
    [string]$failingline,
    
    [Parameter(Mandatory=$false,
                HelpMessage="Powershell command path.")]
    [string]$pscommandpath,    

    [Parameter(Mandatory=$false,
                HelpMessage="Position message.")]
    [string]$positionmsg, 

    [Parameter(Mandatory=$false,
                HelpMessage="Stack trace.")]
    [string]$stacktrace
)
BEGIN { 
        
        $errorlogfile = "$home\Documents\PSlogs\Error_Log.txt"
        $errorlogfolder = "$home\Documents\PSlogs"
        
        if  ( !( Test-Path -Path $errorlogfolder -PathType "Container" ) ) {
            
            Write-Verbose "Create error log folder in: $errorlogfolder"
            New-Item -Path $errorlogfolder -ItemType "Container" -ErrorAction Stop
        
            if ( !( Test-Path -Path $errorlogfile -PathType "Leaf" ) ) {
                Write-Verbose "Create error log file in folder $errorlogfolder with name Error_Log.txt"
                New-Item -Path $errorlogfile -ItemType "File" -ErrorAction Stop
            }
        }
}
PROCESS {  

            Write-Verbose "Start writing to Error log file. $errorlogfile"
            $timestamp = Get-Date 
            #IMPORTANT: Read just first value from collection not the whole collection.
            "   " | Out-File $errorlogfile -Append
            "************************************************************************************************************" | Out-File $errorlogfile -Append
            "Error happend at time: $timestamp on a computer: $hostname - $env - $logicalname" | Out-File $errorlogfile -Append
            "Error message: $errormsg" | Out-File $errorlogfile -Append
            "Error exception: $exception" | Out-File $errorlogfile -Append
            "Failing script: $scriptname" | Out-File $errorlogfile -Append
            "Failing at line number: $failinglinenumber" | Out-File $errorlogfile -Append
            "Failing at line: $failingline" | Out-File $errorlogfile -Append
            "Powershell command path: $pscommandpath" | Out-File $errorlogfile -Append
            "Position message: $positionmsg" | Out-File $errorlogfile -Append
            "Stack trace: $stacktrace" | Out-File $errorlogfile -Append
            "------------------------------------------------------------------------------------------------------------" | Out-File $errorlogfile -Append                   
            
            Write-Verbose "Finish writing to Error log file. $errorlogfile"
}        
END { 

}
}

Step 6: Optionally write a Comment-Based Help

I always write comment-based help for my functions just above the function block in own help region like in this example:

#region help
<#
.SYNOPSIS
Writes errors that occur in powershell scripts into error log file.
.DESCRIPTION
Writes errors that occur in powershell scripts into error log file.
Error log file and error log folder will be created if doesn't exist.
Error log file name is Error_Log.txt and it has been saved into ..\Documents\PSlogs\

.PARAMETER hostname
Name of the computer that is failing.

.PARAMETER env
Environment where computer is located. For example: Production, Acceptance, Test, Course etc.

.PARAMETER logicalname
Type of the server that is failing. For example: Application, Web, Integration, FTP, Scan, etc. 

.PARAMETER errormsg
Error message.

.PARAMETER exception
Error number.

.PARAMETER scriptname
Name of the powershell script that is failing.

.PARAMETER failinglinenumber
Line number in the script that is failing.

.PARAMETER failingline
Content of failing line.

.PARAMETER pscommandpath
Path to the powershell command.

.PARAMETER positionmsg
Error message position.

.PARAMETER stacktrace
Stack trace of the error.

.EXAMPLE
Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace" 

.EXAMPLE
Help Write-ErrorLog -Full

.INPUTS
System.String

InputObject parameters are strings. 
.OUTPUTS
External text file. ..\Documents\PSlogs\Error_Log.txt

.NOTES
FunctionName : Write-ErrorLog
Created by   : Dejan Mladenovic
Date Coded   : 10/31/2018 19:06:41
More info    : http://improvescripting.com/

.LINK 
Out-File
#>
#endregion

TIP: If you want to write comment-based help fast then the best option is to use Windows PowerShell ISE Add-on New-CommentBlock CmdLet which gives an empty help content template that needs just to be filled out. I have explained Add-on CmdLet in the article How To Write PowerShell Function’s Or CmdLet’s Help (Fast).

INFO: If you want in more detail information about each comment-based help keyword and how to write help content step by step approach, please read in the article How To Write PowerShell Help (Step by Step)

Step 7: Optionally, define Execution Examples region

Using region and endregion tags I define Execution Examples region where I put different calls to the function that can be used for testing purposes and function calls with different parameters.

Usually, I put Execution Examples region below function body like in the example that follows:

#region Execution examples
#Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace" -Verbose
#endregion

TIP: Creating of the regions in the code is completely optional. However, regions help us to collapse the code in the labeled region which is a nice way to organize and format the code.

All the regions collapsed

Step 8: Test your PowerShell Function

It is very important to understand that writing own Function or CmdLet is an iterative process and we check our work by testing our function from time to time to see the progress and how far are we to the completion.

For testing purposes, I use function calls that are prepared in Execution examples region.

Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace" -Verbose
Result of test

The test error has been written in the external Error Log text file:

Test error has been written in the external Error Log text file

Step 9: Use PowerShell Debugging as needed

Very often we experience different errors while testing our code and one of the ways to deal with errors is to debug the code.

INFO: PowerShell ISE environment is rich with debugging features which I have described in this article How To Debug PowerShell Scripts.

Step 10: Extra Tip – Benchmark your function execution performance

This step is totally optional but I do it every time I am done with testing the code and CmdLet is ready for production. For that purpose, I have written Measure-BenchmarksCmdLet CmdLet that will show us how many objects are processed and how much time took execution. This CmdLet is part of the Efficiency Booster PowerShell Project which is a library of different CmdLets that helps IT experts in their every day IT tasks.

The source code of Measure-BenchmarksCmdLet CmdLet can be download here and the script is in 02utils folder.

Here is a benchmark example:

Measure-BenchmarksCmdLet {Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace" -Verbose}

We can see that this function executed very fast:

Result of the benchmark with Measure-BenchmarksCmdLet CmdLet

TIP: We can benchmark the code from time to time just to check the performance and compare it with the initial calibration benchmark if we notice a deterioration of performance then we can work on improving the efficiency of code.

Step 11: Import PowerShell CmdLet script into Module

If we want to achieve our goal to make our function to be CmdLet that will have the same look and feel as PowerShell CmdLet delivered by Microsoft we need to do the next two additional steps.

The first of the two is to create a module and add this PowerShell script into a module file using a $psScriptRoot automatic variable.

. $psScriptRoot\WriteErrorLog.ps1

Here is the content of the Utils PowerShell Module file (.psm1):

Utils PowerShell Module (.psm1) content

INFO: If you want to read more about PowerShell Modules please read this article that explains everything you need to know about Modules. How To Create A Powershell Module (Different Approach).

Step 12: Import PowerShell Module In PowerShell Profile

We want the module of the script with function to import in Windows PowerShell Profile file using Import-Module CmdLet. Our example CmdLet Write-ErrorLog is part of the Utils module (02utils) and we import that module using the following syntax:

Import-Module 02utils

Here is the content of PowerShell Profile script file:

PowerShell Profile (.ps1) file content

INFO: Everything you want to know about PowerShell Profiles can be found in the article How To Create PowerShell Profile Step by Step with Examples.

Now our PowerShell CmdLet is ready to be loaded each time new PowerShell Sessions starts with opening either PowerShell Console or ISE.


IMPORTANT: It is key for making PowerShell Function into CmdLet to understand the last two steps and link between PowerShell Function script, PowerShell Module, and PowerShell Profile. So please read following article as well How We Link Together PowerShell Profile, Module, And CmdLet


Step 13: Test your PowerShell CmdLet

After we have done with customizing PowerShell Module and Profile we can test our function as CmdLet.

Before we test the CmdLet we can check whether the module is loaded in our session and is the CmdLet part (command) of the module.

Check PowerShell Modules available in the session.

We use Get-Module CmdLet with ListAvailable parameter to check imported modules into the PowerShell session:

Get-Module -ListAvailable
Utils module is loaded in PowerShell Console Session

Check PowerShell Commands (CmdLets) within the module.

We use Get-Command CmdLet with Module parameter to check all the CmdLets of the Utils module:

Get-Command -Module 02utils
Write-ErrorLog CmdLet is command in the Utils module

Finally, we can test our PowerShell CmdLet:

Result of running Write-ErrorLog CmdLet from PowerShell Console environment

NOTE: Our Write-ErrorLog CmdLet has the same look and feel as PowerShell CmdLets delivered by Microsoft and it is available for us whenever we open new PowerShell session since we made link between Profile, Module, and CmdLet.

Extra Step 14: We can be even more efficient

EXTRA TIP: If we use PowerShell ISE Add-on New-Function CmdLet we can get an empty function template that we need just to fill out. I have explained New-Function CmdLet in the article How To Write Advanced Functions Or CmdLets With PowerShell (Fast).

Here is function template that we get after running New-Function PowerShell ISE Add-on CmdLet:

    #region help
     <#
        .SYNOPSIS
        .DESCRIPTION
        .PARAMETER
        .EXAMPLE
        .INPUTS
        .OUTPUTS
        .NOTES
            FunctionName : 
            Created by   : Dejan
            Date Coded   : 11/16/2019 10:37:30
        .LINK
            
Home
#> #endregion Function { [CmdletBinding()] Param ( ) Begin { } Process { } End { } } #region Execution examples #endregion

I hope we all agree that this is a really efficient starting point to write own functions.


Function Naming Convention

Windows PowerShell recommends that CmdLet and function names have the Verb-Noun format and include an approved verb. It is highly advisable to follow this convention and avoid warning messages while module import functions that don’t follow the approved verbs convention.

Use Get-Verb CmdLet to get the list of approved verbs:

Get-Verb
A partial list of approved PowerShell verbs

Some other useful commands for checking Function or CmdLet names are:

Get-Command -Verb Write 

As a result, we get a list of CmdLets that use the verb Write.

CmdLets that use verb Write

If we want to check which CmdLet has in their name as noun word error we use the following command:

Get-Command -Noun *error*
CmdLets that contain noun error in their name

IMPORTANT: If you want to learn many cool PowerShell CmdLets I have written FREE eBook The Most USEFUL PowerShell CmdLets and more… that you can download here.

FREE eBook The Most USEFUL PowerShell CmdLets and more…

What Is The Structure Of A Windows PowerShell CmdLet

Windows PowerShell CmdLet is PowerShell script function saved as ps1 file extension that consists of several blocks of code:

  • Function block with Function Name (marked blue on the screenshot)
  • Optional, Help region (marked green on the screenshot)
  • Optional, Execution Examples Region (marked black on the screenshot)
    • Different Calls to Function which will test input parameters
Structure of PowerShell CmdLet and Advanced Function

In addition, we can have the PowerShell Module file saved as a psm1 file extension where we will import our PowerShell CmdLet script using a $psScriptRoot automatic variable. Optionally, with the module file, we can have the PowerShell Module Manifest file saved as a psd1 file extension where we will further describe our Module.


Finally, we can have a PowerShell Profile file saved as PowerShell script ps1 where we will import the module with Import-Module CmdLet.


How To Link PowerShell CmdLet Function Script, Module, And Profile

It is very important for writing own PowerShell CmdLets to understand the link between PowerShell Function saved as a script (.ps1) and PowerShell Module saved as (.psm1) and PowerShell Profile saved as a script (.ps1). Please read How We Link Together PowerShell Profile, Module, And CmdLet for detailed instructions on how to create that link.

What Is Happening When We Run Windows PowerShell Console Or ISE

Here is an explanation of the PowerShell Workflow when every session starts. Hopefully, this will help you better understand previous steps.

When we open the Windows PowerShell Console or ISE here are things that happen in the background even before we see the Console or ISE window:

  • The new PowerShell session starts for the current user.
  • PowerShell profile files have been loaded into the session.
  • The profile file will import modules. In our example, it will load the 02utils module among the others. (Remember: Use Get-Module CmdLet with parameter ListAvailable to see imported modules in the session)
  • When the modules are imported the code inside the Script module file (.psm1) is executed. In our example, $psScriptRoot\WriteErrorLog.ps1 script file will be executed. (Remember: Use Get-Command CmdLet with Module parameter to see the commands that module contains).
  • Finally, our environment is customized and we see the Windows PowerShell Console or ISE. Now we can start using our own PowerShell CmdLet.

Useful PowerShell CmdLet Articles

Here are some useful articles and resources:


REMEMBER To PowerShell Every Day!


About Dejan Mladenović

Post Author Dejan MladenovicHey Everyone! I hope that this article you read today has taken you from a place of frustration to a place of joy coding! Please let me know of anything you need for Windows PowerShell in the comments below that can help you achieve your goals!
I have 18+ years of experience in IT and you can check my Microsoft credentials. Transcript ID: 750479 and Access Code: DejanMladenovic
Credentials
About Me...

My Posts | Website

Dejan Mladenović

Hey Everyone! I hope that this article you read today has taken you from a place of frustration to a place of joy coding! Please let me know of anything you need for Windows PowerShell in the comments below that can help you achieve your goals! I have 18+ years of experience in IT and you can check my Microsoft credentials. Transcript ID: 750479 and Access Code: DejanMladenovic
Credentials About Me...

Recent Posts

How To Create Custom PowerShell CmdLet