Powershell: How to Structure a Module

There doesn’t seem to be much guidance as to the internal structure of a Powershell module. There’s a lot of “you can do it this way or that way” guidance, but little “this has worked well for me and that hasn’t.” As a patterns and practices guy, I’m dissatisfied with this state of affairs. In this post I will describe the module structure I use and the reasons it works well for me.

I’ve captured the structure in a sample module for you to reference.

Powershell Module Structure

Posh.psd1

This is a powershell module manifest. It contains the metadata about the powershell module, including the name, version, unique id, dependencies, etc..

It’s very important that the Module id is unique as re-using a GUID from one module to another will potentially create conflicts on an end-user’s machine.

I don’t normally use a lot of options in the manifest, but having the manifest in place at the beginning makes it easier to expand as you need new options. Here is my default psd1 implementation:

# Version number of this module.
ModuleVersion = '1.0'

# Supported PSEditions
# CompatiblePSEditions = @()

# ID used to uniquely identify this module
GUID = '2a97124e-d73e-49ad-acd7-1ea5b3dba0ba'

# Author of this module
Author = 'chmckenz'

# Company or vendor of this module
CompanyName = 'ISG Inc'

# Copyright statement for this module
Copyright = '(c) 2018 chmckenz. All rights reserved.'

ModuleToProcess = "Posh.psm1"

Posh.psm1

This is the module file that contains or loads your functions. While it is possible to write all your module functions in one file, I prefer to separate each function into its own file.

My psm1 file is fairly simple.

gci *.ps1 -path export,private -Recurse | %{
. $_.FullName
}

gci *.ps1 -path export -Recurse | %{
Export-ModuleMember $_.BaseName
}

The first gci block loads all of the functions in the Export and Private directories. The -Recurse argument allows me to group functions into subdirectories as appropriate in larger modules.

The second gci block exports only the functions in the Export directory. Notice the use of the -Recurse argument again.

With this structure, my psd1 & psd1 files do not have to change as I add new functions.

Export Functions

I keep functions I want the module to export in this directory. This makes them easy to identify and to export from the .psm1 file.

It is important to distinguish functions you wish to expose to clients from private functions for the same reason you wouldn’t make every class & function public in a nuget package. A Module is a library of functionality. If you expose its internals then clients will become dependent on those internals making it more difficult to modify your implementation.

You should think of public functions like you would an API. It’s shape should be treated as immutable as much as possible.

Private Functions

I keep helper functions I do not wish to expose to module clients here. This makes it easy to exclude them from the calls to Export-ModuleMember in the .psm1 file.

Tests

The Tests directory contains all of my Pester tests. Until a few years ago I didn’t know you could write tests for Powershell. I discovered Pester and assigned a couple of my interns to figure out how to use it. They did and they taught me. Now I can practice TDD with Powershell–and so can you.

Other potential folders

When publishing my modules via PowershellGallery or Chocolatey I have found it necessary to add additional folders & scripts to support the packaging & deployment of the module. I will follow-up with demos of how to do that in a later post.

Summary

I’ve put a lot of thought into how I structure my Powershell modules. These are my “best practices,” but in a world where Powershell best practices are rarely discussed your mileage may vary. Consider this post an attempt to start a conversation.

3 thoughts on “Powershell: How to Structure a Module

  1. Interesting approach Chris, I can see this being useful for at a glance inventory of a module’s functions and the ability to add new functions by just creating a file for the function. I had been using the traditional approach similar to what you have but containing all the code within the psm1 file. We develop institutional modules, this could make the development/support cycle just a little more efficient. The one paradox is that you retweeted a quote about clean code. While some could consider the .psm1 file clean code, general consensus for best practice is to use function full names and named parameters.

    For those reading this here is the equivalent:

    Dot source the functions into the current session

    Get-ChildItem -Filter *.ps1 -Path Export,Private -Recurse | ForEach-Object { . $_.FullName }

    Export the functions in the Export folder

    Get-ChildItem -Filter *.ps1 -Path Export -Recurse | ForEach-Object { Export-ModuleMember $_.BaseName }

    1. In general I agree about using function full-names and about using named parameters. However, gci, %, ?, and select are so common as aliases I don’t think it creates any confusion to use them.

      To my way of thinking this is analogous to the advice against single-letter variable names. It’s normally bad practice but i, j, and k are used so often as loop counters that they’re fine to use so long as they’re used as loop counters.

      Thanks for the comment!

Leave a Reply to jcoryatjrJimCancel reply

%d bloggers like this: