Sunday, March 14, 2021

PowerShell Module Quick Start

PowerShell modules are a great way to organize related functions into reusable unit. Today's post will provide a simple walk-through that creates a bare-bones module that you can start to work with.

Table of Contents

Why create a Module?

First of all, let's look at some of the motivations for creating a module - after all, you could simply move all your common utility methods into a 'helper' script and then import it into the session. Why go through the overhead of creating a module?

Not every script or utility function needs to be a module, but if you're creating a series of scripts that rely on some common helper functions, I can think of two primary reasons why you should consider creating a module:

  • Pathing: when you import a script using "dot sourcing", the "." means the current working folder. If you're import individual script files, you need to specify the relative path of the file you're importing. This strategy starts to fall apart if the file you're importing references additional imports. However, when you create a module, you import all the scripts into the session so pathing no longer becomes an issue.
  • Discoverability: PowerShell scripts can get fairly complex so if your helper utility contains dozens of smaller methods then developers consuming your script will need to 'grok' all of those functions, too. By creating a module, you can expose only the functions you want, which will make it easier for developers to understand and use.

1. Create the Manifest Module

A module is comprised of:

  • <module-name>.psd1: a manifest file that describes important details about the module (name, version, license, dependencies, etc)
  • <module-name>.psm1: the main entry point when the module is imported.

Important Note: The folder and module manifest name must match!

To simplify this process, Microsoft has provided the New-ModuleManifest cmdlet for us. They have some good guidance listed here on some additional settings you may want to provide, but here's the bare-bones nitty-gritty:

mkdir MyModule cd MyModule New-ModuleManifest -Path .\MyModule.psd1 -RootModule MyModule.psm1

2. Public vs Private Folders

When you're creating a module, it's best to think about what functions and features that you're exposing to your module consumers. This is very similar to the access-modifiers we put on classes in our .NET assemblies.

The less you expose, the easier it is for consumers to understand what your module does. Limiting what you expose can also protect you from accidentally introducing breaking changes to consumers - if all of your functions are public you won't know which methods that external team members might be using; keeping this list small can help you focus where version compatibility is required.

A good practice is to put our functions into two folders: public and private:

mkdir Private mkdir Public

3. Create the Module Script (psm1)

The last piece is the psm1 script. This simple script finds all the files and imports them into the session:

# MyModule.psm1  
# Get Functions 
$private = Get-ChildItem -Path (Join-Path $PSScriptRoot Private) -Include *.ps1 -File -Recurse
$public = Get-ChildItem -Path (Join-Path $PSScriptRoot Public) -Include *.ps1 -File -Recurse  

# Dot source to scope 
# load private scripts first 
($private + $public) | ForEach-Object {
     try {
         Write-Verbose "Loading $($_.FullName)"
         . $_.FullName
     catch {
         Write-Warning $_.Exception.Message

# Expose public functions. Assumes that function name and file name match 
$publicFunctions = $public | Select-Object -ExpandProperty BaseName Export-ModuleMember -Function $publicFunctions

The last section of the script exposes the functions in your public folder as part of your module. This makes the assumption that the function name and file name are the same. An alternative to this approach is to set the Functions to export in the module manifest. I find this approach easier.

4. Using the Module

At this point, you're ready to start adding PowerShell scripts into your module. When you're ready to try it out, simply import the module by it's definition:

Import-Module .\MyModule.psd1 -Verbose

To verify that your commands are exposed correctly:

(Get-Module MyModule).ExportedCommands

During development of your module, it's important to realize that changes that are made to your scripts won't be visible until you reload the module:

Remove-Module MyModule -ErrorAction Silent Import-Module .\MyModule.psm1

What's Next?

There's lots more to creating a PowerShell module, such as setting the minimum supported PowerShell version, declaring dependencies, including .NET code assemblies, exposing types, etc. There are likely some additional considerations for publishing the module to a gallery, but unfortunately I'm not going to get into that for the purposes of this post. This article is helpful for creating the basic shell of a module, which you can use locally or on build servers.

Wrap Up

Creating a PowerShell module is a fairly quick process that helps to promote reuse between related scripts. With Microsoft's New-ModuleManifest cmdlet and the basic psm1 file provided above, you can fast track creating a Module.

Happy codin'