Welcome to this new post! Today, I’ll show you how to set up and work with PowerShell modules to enhance the efficiency and maintainability of your code.

Before diving into the details, let’s quickly recap the concepts from previous posts, as they will be relevant here as well.

In my previous blog posts, you’ve learned how to elevate your scripting skills. We’ve covered topics such as:

  • Object-oriented scripting
  • Error handling
  • Advanced functions
  • Function attributes
  • Data structuring

Most importantly, we focused on writing well-structured and organized code ensuring that your code is not only readable, but that it is clear and understandable even months later—both for you and for others who may need to use it.

In this post, I’ll introduce you to PowerShell modules, an essential way to structure related code into reusable components. Let’s get started!

Modules! Wait, what?

PowerShell modules are a powerful way to bring your scripting together. To put it in a metaphor:

“Imagine you’re a plumber working on multiple projects at once. Instead of carrying around a bunch of loose tools, you’d likely organize them into toolboxes based on their purpose. You might have a toolbox for measuring tools, another for soldering tools, and perhaps one for construction or demolition tools each with its own designated spot so you can quickly find and access what you need.”

In the same way, whenever you need a specific script tool, you know exactly where to look in your modules- toolbox. Instead of rummaging through your entire workbench, you simply pick the right toolbox that contains your tool.

Structure

When you start working with modules, you’ll encounter files with different extensions. PowerShell scripts are identified by the .psm1 extension and are often shipped alongside a .psd1 file.

Let’s stick with the toolbox metaphor to explain the differences between these two.

  • PSM1 File – The PSM1 file contains all the business logic and the code which is run when the module is called. You can see this as the ‘set of tools’ in our toolbox. Like all the tools required for doing that construction job. When you put this module to work you basically open the toolbox and make sure that the tools in there are available for using.
  • PSD1 File – The PSD1 file is like the label on the toolbox that tells you what’s inside. If you have a very large toolbox or multiple toolboxes next to each other, you don’t want to browse through all of them just to find that one specific tool. The PSD1 file also contains information like ‘Who made the tool’ and ‘how to use the tool’  

So, whenever you load a new toolbox, it’s like grabbing a well-organized set of tools designed to get the job done for a specific use case.


💡 When creating new toolboxes, keep this metaphor in mind! Just as you wouldn’t store construction tools in the same box as soldering tools, apply the same logic here keep related functions together, just as you would in real life.

Creating the module

PowerShell module files are identified by the .psm1 extension. You can create a new file with this extension and add functions to start building your module.

In previous posts, I focused on a user management system, and I will continue using the same example here.

class User {
    [string] $Name
    [string] $Email
    [string] $Phone

    User([string] $Name, [string] $Email, [string] $Phone) {
        $this.Name = $Name
        $this.Email = $Email
        $this.Phone = $Phone
    }

    [string] ToString() {
        return "Name: $($this.Name), Email: $($this.Email), Phone: $($this.Phone)"
    }
}

class UserManagement {
    
    [User[]] $Users

    UserManagement() {
        $this.Users = @()

        $this.Users += [User]::new("John Doe", "john.doe@bartpasmans.tech", "123456789")
        $this.Users += [User]::new("Jane Doe", "jane.doe@bartpasmans.tech", "987654321")
    }
}

$userManagement = [UserManagement]::new()

function Set-User {}

function Get-User {}

function Remove-User {}

This code creates an instance of the user management system and automatically adds the two tracked users to the collection.
Now, ensure you have opened a terminal and navigated to the directory where the .psm1 file is located. Then, run the following command:

New-ModuleManifest -path .\example.psd1 -RootModule .\example.psm1


👉 In this example, the module file is named example.psm1. Be sure to replace it with the actual name of your module file when running the command.

New manifest!

As explained in the previous chapter, the module manifest (PSD File) describes what’s in our toolbox. Once the generation is complete, we essentially have a toolbox describer!

After running the command, open the newly created .psd1 file and review its contents. You’ll find a variety of configurable settings that can help enhance your module. Below are some key settings along with explanations of what they do.

  • Description – Provide a description of what your module does. This helps others understand ‘when to use your module’
  • RequiredModules – Other modules that must be installed for your module to work. Modules don’t always have to be standalone: they can be like a family tree, consisting of many modules to create a big framework for your automation!
  • RequiredModules – Other modules that must be installed for your module to work. Modules don’t always have to be standalone: they can be like a family tree, consisting of many modules to create a big framework for your automation!
  • FunctionsToExport – This is where the functions are described which are available outside of the module. Without specifying a wildcard or the correct function names, your module will be useless.

There are many more configuration options available, but the ones mentioned above are likely the most commonly used and relevant for creating your first module.

Now, let’s modify the exported function configurations. In the module, I’ve configured three functions.

function Set-User {}

function Get-User {}

function Remove-User {}

Lets make sure that the manifest reflects this and that only these functions get exported when the module is being imported. In the end it should look like this:

FunctionsToExport = 'Set-User, Get-User, Remove-User'

For the first run, let’s modify the functions to generate some output.

For now, a simple string output is enough to confirm that the module is working as expected.

function Set-User {
    return "Set User Called"
}

function Get-User {
    return "Get User Called"
}

function Remove-User {
    return "Remove User Called"
}

💡 When importing modules into an existing PowerShell session, PowerShell first checks whether the module is already loaded. If the module is present in the current session, it will not automatically reload unless explicitly forced.

Now, open a terminal session and import the module. Ensure that you have already navigated to the correct directory where your module is located.

Import-Module .\example.psm1 

Where in this case ‘example.pms1’ is the name of my module file.

💡 You can easily discover the functionality of a module by running the command:

Get-Command -Module [module-name]

In this case, replace [module-name] with example, and you’ll see a list of functions available in the module.

Since the import now loads everything the module exports, we can call all its functions.

Executing these functions returns the output exactly as described in the module.

Commenting

Commenting is one of the most important aspects of writing code, especially when preparing modules for distribution to other users.

Clear comments help others understand your modules, how to use them, which parameters they accept, and the types of values those parameters allow.

The code below demonstrates a well-commented function with parameters, staying consistent with our previous code examples.

<#
.SYNOPSIS
    Brief description of what the function does.

.DESCRIPTION
    Detailed description of what the function does, how it works,
    and any important information users should know.

.PARAMETER UserName
    The username to process. Must be a valid Active Directory username.
    Accepts pipeline input.

.PARAMETER Department
    The department to assign to the user. 
    Valid values are: 'IT', 'HR', 'Finance', 'Sales'

.PARAMETER Force
    If specified, suppresses confirmation prompts.

.EXAMPLE
    Set-User -UserName "john.doe" -Department "IT"
    Creates or updates user john.doe in the IT department.

.EXAMPLE
    Get-ADUser -Filter * | Set-User -Department "Sales" -Force
    Updates all AD users to the Sales department without confirmation.

.INPUTS
    System.String for UserName
    System.String for Department

.OUTPUTS
    System.Boolean
    Returns $true if successful, $false if failed.

.NOTES
    Author: Bart Pasmans
    Version: 1.0
    Date: 2025-02-12
    Requires: ActiveDirectory module
    Change Log:
    - 1.0: Initial release
    - 1.1: Added Force parameter

.LINK
    https://your-documentation-link
    https://related-powershell-docs

.COMPONENT
    The component/technology this function belongs to (e.g., ActiveDirectory)
#>
function Set-User {
    [CmdletBinding(
        SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium',
        DefaultParameterSetName = 'Default'
    )]
    param (
        # Username to process
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "Enter the username to process"
        )]
        [ValidateNotNullOrEmpty()]
        [string]$UserName,

        # Department to assign
        [Parameter(Mandatory = $true)]
        [ValidateSet('IT', 'HR', 'Finance', 'Sales')]
        [string]$Department,

        # Force parameter to skip confirmations
        [Parameter()]
        [switch]$Force
    )

    begin {
        # Initialize any resources
        Write-Verbose "Starting Set-User function"
        $successful = $false
    }

    process {
        try {
            # Main function logic here
            Write-Verbose "Processing user: $UserName"
            
            if ($Force -or $PSCmdlet.ShouldProcess($UserName, "Set department to $Department")) {
                # Actual work happens here
                Write-Information "Setting department for $UserName to $Department"
                $successful = $true
            }
        }
        catch {
            Write-Error "Failed to process user $UserName: $_"
            $successful = $false
        }
    }

    end {
        Write-Verbose "Completed Set-User function"
        return $successful
    }
}

Description

  • Synopsis – A brief (one-line) description of the function.  This appears when running Get-Help FunctionName. Think of it as your “elevator pitch” description.
  • Description A detailed description of the function.
    • What it doesHow it worksImportant notesMultiple linesare allowed
  • Parameter Describes each parameter:
    • What it’s forExpected valuesAny constraintsDefault valuesWhether it’s mandatory
  • Example Provides examples of how to use the function.
    • Should include the command and its output.
    • Multiple .EXAMPLE sections are encouraged to show different use cases.
  • Inputs Describes what type of input the function accepts:
    • The .NET type of objects that can be piped in
    • Whether it accepts pipeline input by value or by property name
    • Example: System.String for username input

Of course there are more fields you can utilize to describe what your function does but hopefully these already help you gaining a better understanding on how to utilize them.

Ready to ship!

Now that the module is setup you can ship it. For instance towards your elevated users who can utilize it to make their life easier. But also to only communities (PowerShell gallery). If you commit to the standards as I’ve shown in previous posts and by commenting your functions like shown above, your code will be understandable and readable by many others.

If you want to know more about how to do the shipping of your modules to for instance online communities let me know by replying below 👇

Summary

In this post, we’ve explored PowerShell modules as a powerful way to organize and structure your code. We’ve covered:

What Modules Are: Using the toolbox metaphor to explain how modules help organize related PowerShell code into manageable, reusable components.

Module Structure

  • PSM1 files containing the actual code (the tools)
  • PSD1 manifest files describing the module (the toolbox label)

Creating Modules

  • How to create basic module files
  • Generating module manifests using New-ModuleManifest
  • Configuring important manifest settings like FunctionsToExport

Module Usage

  • Importing modules using Import-Module
  • Discovering module functions with Get-Command
  • Working with exported functions

Best Practices

  • Proper function commenting for documentation
  • Organizing related functionality together
  • Making modules discoverable and user-friendly

Whether you’re writing scripts for yourself or sharing them with others, modules provide a structured way to package and distribute your PowerShell code. By following these practices, you’ll create more maintainable and professional PowerShell solutions.

Next time you’re working on PowerShell scripts, consider whether organizing them into a module could make your code more manageable and reusable!

Leave a Reply

Your email address will not be published. Required fields are marked *