• Blog
  • Webinars
  • Partner
  • Support
  • Contact
  • EN
    • DE
ScriptRunner
  • About us
    • Team
    • Jobs
    • Press
  • Why
  • Use Cases
  • Software
    • ScriptRunner Server
    • ScriptRunner Portal
    • ScriptRunner Portal Widget
    • ScriptRunner Connectors
    • ScriptRunner ActionPacks
  • Try Now
  • Search
  • Menu Menu
You are here: Home1 / ScriptRunner Blog2 / News3 / Building Your First PowerShell Module

Building Your First PowerShell Module

Author: Adam Bertram | Reading time: 5 minutes | Category: News, Scripting

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.

Table of content

  • What is a PowerShell Module?
  • Defining the Module’s Purpose
  • Structure of a Module
  • Creating the Module
  • Creating our Functions
  • Conclusion
Article: Building your first PowerShell module - Adam Bertram

What is a PowerShell Module?

At its most basic, a PowerShell module consists of two required components and one optional. Together these components are the fewest recommended components to use in 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

Crafting a module is not difficult, but structuring the exposed functionality can be. The purpose, of this article, is to create a simple module with three functions. Often modules contain functionality related to a single product or tool. The functions in our module target three different products but support a specific workflow. The workflow is to retrieve SharePoint members, trigger an onboarding Flow, and finally send the SharePoint group members in a Team’s message.

  • New-TeamsMessage
  • Invoke-PowerAutomateFlow
  • Get-SharePointMember

Structure of a Module

The structure of the module itself is pretty simple. The folder and file layout are as described below. One question you may have is why we prepend func_ to the function file. This is a personal preference and not necessary. This technique is useful to avoid dot-sourcing errant scripts in the module’s directories. You will see how to use this technique in the module loading file.

Public

  • func_New-TeamsMessage.ps1
  • func_Invoke-PowerAutomateFlow.ps1
  • func_Get-SharePointMember.ps1

Private – Empty

  • UtilityModule.psd1
  • UtilityModule.psm1

Creating the Module

The fastest way to create a module definition file is the New-ModuleManifest command. Below, we are defining a handful of parameters that will create a usable module. CompatiblePSEditions defines both Desktop and Core as supported PowerShell versions. Command discovery works if you populate the FunctionsToExport parameter.

$Params = @{ 
		"Path" 				= 'D:WorkingFolderArticlesUtilityModule.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

After creating the module, we need to define our functions. In this example, we are creating three simple functions that support the intended workflow.

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

Creating a module could not be simpler in PowerShell. To make a system administrator’s life easy, bundle common functions together into a module. There is a lot more functionality available and this tutorial only scratches the surface. Exploring the possibilities that PowerShell modules offer will unlock an easier administrative experience!

Share this article
  • Share on Facebook
  • Share on Twitter
  • Share on WhatsApp
  • Share on LinkedIn
  • Share on Reddit
  • Share by Mail

These articles might also be interesting for you:

Article: Introducing Three Popular PowerShell Editors - Adam BertramScriptRunner Software GmbH

Software Spotlight: Introducing Three Popular PowerShell Editors

https://www.scriptrunner.com/wp-content/uploads/2021/02/powershell-editors.gif 1000 1000 Adam Bertram https://www.scriptrunner.com/wp-content/uploads/2018/05/ScriptRunner_Logo_RGB-300x45.png Adam Bertram2021-02-10 10:00:112021-02-25 12:41:38Software Spotlight: Introducing Three Popular PowerShell Editors
Article: X-mas Fun with PowerShell and ScriptRunnerScriptRunner Software GmbH

X-mas Fun with PowerShell and ScriptRunner

https://www.scriptrunner.com/wp-content/uploads/2020/12/xmas-fun-powershell.jpg 1000 1000 Bruno Buyck https://www.scriptrunner.com/wp-content/uploads/2018/05/ScriptRunner_Logo_RGB-300x45.png Bruno Buyck2020-12-23 10:00:412021-01-07 16:26:55X-mas Fun with PowerShell and ScriptRunner
Article image: PowerShell AliasingScriptRunner Software GmbH

PowerShell Aliasing

https://www.scriptrunner.com/wp-content/uploads/2020/11/powershell-aliasing.png 1000 1000 Marco Kamner https://www.scriptrunner.com/wp-content/uploads/2018/05/ScriptRunner_Logo_RGB-300x45.png Marco Kamner2020-12-02 10:00:072021-01-07 16:33:54PowerShell Aliasing

About the author:

Adam Bertram
Adam Bertram

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:

  • Article: Using PowerShell to Create New Citrix PVS MachinesScriptRunner Software GmbHUsing PowerShell to Create new Citrix PVS Machines4. March 2021 - 9:00
  • ScriptRunner sponsors the Scottish Summit 2021ScriptRunner sponsors the Scottish Summit 202116. February 2021 - 10:00
  • Article: Introducing Three Popular PowerShell Editors - Adam BertramScriptRunner Software GmbHSoftware Spotlight: Introducing Three Popular PowerShell Editors10. February 2021 - 10:00
  • Article: ScriptRunner 2020R2ScriptRunner Software GmbHScriptRunner 2020R23. February 2021 - 11:00
  • Article image: ScriptRunner Azure and M365 - a perfect trio, by Frank KresseScriptRunner Software GmbHScriptRunner, Azure and M365 – a perfect trio27. January 2021 - 14:21

Product

  • ScriptRunner Platform
  • ScriptRunner Server
  • ScriptRunner Portal
  • ScriptRunner Portal Widget
  • ScriptRunner Apps
  • ScriptRunner Connectors
  • Script Collections
  • Licensing
Get your free trial

Solutions

  • IT Administrators
  • IT Team Leaders
  • Use Cases

Resources

  • Blog
  • Documentation
  • Knowledge Base
  • Webinars
  • PowerShell Lexicon
  • PowerShell Poster
  • PowerShell Security Ebook

Company

  • About us
  • Team
  • Jobs
  • Press
  • References
  • Partner

Contact

ScriptRunner Software GmbH
Ludwig-Erhard-Straße 2
76275 Ettlingen
Germany

T: +49 7243 20715-0
M: info(at)scriptrunner.com

Request Demo
© ScriptRunner Software GmbH is a subsidiary of AppSphere AG
  • LinkedIn
  • Xing
  • Twitter
  • Facebook
  • Youtube
  • Imprint
  • Privacy Policy
  • Newsletter
Getting Loopy: An Introduction to PowerShell Loops Getting Loopy: An Introduction to PowerShell LoopsScriptRunner Software GmbH Heiko Brenn at Microsoft IgniteScriptRunner Software GmbH Heiko Brenn at Microsoft Ignite
Scroll to top