ScriptRunner Blog
Building Your First PowerShell Module
Table of Contents
PowerShell has a variety of powerful cmdlets built-in to the core language. But native functions only take you so far, and in most projects you will need more functionality. The PowerShell Gallery offers a wide range of downloadable third-party modules, but there’s also the option to create custom PowerShell modules. This article will guide you through the process of creating your first PowerShell module.
What is a PowerShell Module?
- psd1 File – PowerShell definition file
- psm1 File – PowerShell module loading file
- Functions
There are two types of modules, script modules, and binary modules. In this example, we are building a script module. Consisting of traditional PowerShell functions, a script function is easy to build. A binary module is a .NET framework compiled assembly. Written in .NET, cmdlets are not as approachable as script modules. First, we need to define the purpose of the PowerShell module. Not all aspects of a module need mapping before starting. Creating a clear plan makes defining necessary functions much easier.
Defining the Module’s Purpose
Structure of a Module
Public
- func_New-TeamsMessage.ps1
- func_Invoke-PowerAutomateFlow.ps1
- func_Get-SharePointMember.ps1
Private – Empty
- UtilityModule.psd1
- UtilityModule.psm1
Creating the Module
$Params = @{ "Path" = 'D:\WorkingFolder\Articles\UtilityModule.psd1' "Author" = 'Fake Author' "CompanyName" = 'Fake Company' "RootModule" = 'UtilityModule.psm1' "CompatiblePSEditions" = @('Desktop','Core') "FunctionsToExport" = @('Get-SharePointMember','Invoke-PowerAutomateFlow','New-TeamsMessage') "CmdletsToExport" = @() "VariablesToExport" = '' "AliasesToExport" = @() "Description" = 'Utility Module' } New-ModuleManifest @Params
We export functions using Export-ModuleMember, but performance best practices dictate the use of empty arrays in the module definition. For unknown reasons, an empty array does not export correctly under VariablesToExport, but an empty string does output an empty array.
Running the New-ModuleManifest command creates a UtilityModule.psd1 file. Many comment blocks pertain to extra configurations. Below, we have removed all extra comments to show the configured parameters.
UtilityModule.psd1
@{ RootModule = 'UtilityModule.psm1' ModuleVersion = '0.0.1' CompatiblePSEditions = 'Desktop', 'Core' GUID = 'dc18a919-f4bf-4da2-8c76-24b68fa33ef0' Author = 'Fake Author' CompanyName = 'Fake Company' Copyright = '(c) Fake Author. All rights reserved.' Description = 'UtilityModule' FunctionsToExport = 'Get-SharePointMember','Invoke-PowerAutomateFlow','New-TeamsMessage' CmdletsToExport = @() VariablesToExport = @() AliasesToExport = @() PrivateData = @{ PSData = @{} } }
If all module functions are dot-sourced, then why do we need to list the functions to export? There are two primary reasons for explicitly listing exported functions. Private functions should not be made public, and exported functions will auto-complete on the command line even if the module is not yet loaded.
Next we need to create the module loading file. Again, there are several ways to approach this, but the below method has been reliable. The first section will only retrieve func_ prefixed files and dot-source them. The second will export all functions in the Public folder.
UtilityModule.psm1
Get-ChildItem (Split-Path $script:MyInvocation.MyCommand.Path) -Filter 'func_*.ps1' -Recurse | ForEach-Object { . $_.FullName } Get-ChildItem "$(Split-Path $script:MyInvocation.MyCommand.Path)\Public\*" -Filter 'func_*.ps1' -Recurse | ForEach-Object { Export-ModuleMember -Function ($_.BaseName -Split "_")[1] }
Creating our Functions
New-TeamsMessage
This function will take in a message string and team ID string and create the REST call to create a Teams message. This wraps the necessary JSON formatting into a simplified API call.
Function New-TeamsMessage { [CmdletBinding()] Param( [Parameter(Position = 0, Mandatory = $true)][String]$Message, [Parameter(Position = 1, Mandatory = $true)][String]$Title, [Parameter(Position = 2, Mandatory = $true)][String]$URI ) Process { $Params = @{ "URI" = $URI "Method" = 'POST' "Body" = [PSCustomObject][Ordered]@{ "@type" = 'MessageCard' "@context" = 'http://schema.org/extension' "summary" = $Title "title" = $Title "text" = ($Message | Out-String) } "ContentType" = 'application/json' } Invoke-RestMethod @Params | Out-Null } }
Invoke-PowerAutomateFlow
Next, we are defining a Power Automate Flow function. There is a simple way to do this by using the HTTP trigger, “When a HTTP request is received”. This uses the given URI displayed upon saving to call the trigger.
Function Invoke-PowerAutomateFlow { [CmdletBinding()] Param( [Parameter(Position = 0, Mandatory = $true)][String]$URI ) Process { $Params = @{ "URI" = $URI "ContentType" = 'application/json' "Method" = 'GET' } Invoke-WebRequest @Params } }
Get-SharePointMember
Get-SharePointMember supports our final workflow need of retrieving a SharePoint member list. This function gets all SharePoint site members for a given site and displays the results. Passing in a group will filter the results.
Function Get-SharePointMember { [CmdletBinding()] Param( [Parameter(Position = 0, Mandatory = $true)][String]$URI, [Parameter(Position = 1)][String]$Group ) Process { $Params = @{ "URI" = $URI } If ($Group) { Get-SPOUser @Params | Where-Object -Contains $Group } Else { Get-SPOUser @Params } } }
Importing and Testing the Module
Import the module using the Import-Module command and verify there are no errors. Next, we will use Get-Command to see all exported members.
Import-Module -Name 'UtilityModule' Get-Command -Module 'UtilityModule'
To test, run the various commands and make sure that they are functioning as you would expect. In the script below we are running the functions and utilizing their output.
# Get SharePoint Members $Members = Get-SharePointMember # Invoke Invoke-PowerAutomateFlow -URI 'https://...' New-TeamsMessage -Message $Groups -Title 'Group Members' -URI 'https://...'
Conclusion
Related posts
6 min read
Boost your IT automation efficiency with new ScriptRunner release
Sep 30, 2024 by Frank Kresse
We have just released our latest ScriptRunner update, version 7.1, packed with powerful new features aimed at making IT...
8 min read
Scriptember 2024 – Celebration of PowerShell and its community
Aug 16, 2024 by Heiko Brenn
Welcome to Scriptember! We are thrilled to announce the launch of a unique, month-long campaign dedicated to...
10 min read
Five reasons you should be using PSReadLine
Aug 14, 2024 by Jeffery Hicks
I'd like to think that because you are reading this, you are a professional PowerShell user and this article will be a...
About the author:
Adam is a 20+ year veteran of IT and experienced online business professional. He’s an entrepreneur, IT influencer, Microsoft MVP, blogger, trainer, author and content marketing writer for multiple technology companies.
Latest posts:
- Boost your IT automation efficiency with new ScriptRunner release
- Scriptember 2024 – Celebration of PowerShell and its community
- Five reasons you should be using PSReadLine
- Privacy Management with PowerShell – Let's look at the core features of Priva!
- Let's celebrate System Administrator Appreciation Day!