Skip to content

09 · Modules Basics

A module is a packaged, reusable unit of PowerShell code — functions, variables, and more — that you can load with Import-Module. Every cmdlet you've used so far (Get-Process, Get-Content, ...) actually lives in a built-in module.

Seeing what's loaded and available

Get-Module                          # modules currently loaded in this session
Get-Module -ListAvailable            # all modules installed on this machine
# find which module a cmdlet comes from
Get-Command Get-Process | Select-Object Name, ModuleName
Name        ModuleName
----        ----------
Get-Process Microsoft.PowerShell.Management

The PowerShell Gallery is the community package repository (like PyPI for Python or npm for JavaScript).

# search for a module
Find-Module -Name "Pester"

# install for the current user (no admin rights needed)
Install-Module -Name Pester -Scope CurrentUser -Force

# import it into the current session
Import-Module Pester

Writing your own simple module

A module can be as simple as a .psm1 file containing one or more functions.

# MathHelpers.psm1
function Get-Square {
    param([int]$Number)
    return $Number * $Number
}

function Get-Cube {
    param([int]$Number)
    return $Number * $Number * $Number
}

Export-ModuleMember -Function Get-Square, Get-Cube
# using it from another script in the same folder
Import-Module ./MathHelpers.psm1

Get-Square -Number 5    # 25
Get-Cube -Number 3       # 27

Export-ModuleMember controls what's visible from outside the module — without it, every function defined in the file is exported by default, but being explicit is good practice once a module grows.

Module search paths

$env:PSModulePath -split [System.IO.Path]::PathSeparator

Modules placed in one of these standard locations (a per-user modules folder, in a subfolder matching the module's own name) can be imported by name alone, without a path:

Import-Module MathHelpers    # works if MathHelpers/MathHelpers.psm1 is on $env:PSModulePath

Removing a module from the session

Remove-Module MathHelpers

This unloads the module's functions from the current session (useful while iterating on a module's code) without uninstalling anything from disk.

Dot-sourcing vs importing

For a single script file full of helper functions (not a full module), you can also "dot-source" it — this runs the file in the current scope rather than a separate module scope.

# helpers.ps1
function Get-Greeting { param($Name) "Hello, $Name!" }
# main.ps1
. ./helpers.ps1        # note the leading ". " with a space

Get-Greeting -Name "Ada"   # Hello, Ada!

Dot-sourcing is simple and fine for small scripts; real reusable code that you'll Import-Module from many places should be a proper module (covered in depth in Level 3).

Cheat sheet

Command Purpose
Get-Module / Get-Module -ListAvailable list loaded / installed modules
Find-Module search the PowerShell Gallery
Install-Module -Scope CurrentUser install a module for your user
Import-Module <name-or-path> load a module into the session
Export-ModuleMember -Function ... control what a module exposes
Remove-Module unload a module from the session
. ./script.ps1 dot-source a script into the current scope

How It Actually Works

Importing a module is not "running a script" in the way dot-sourcing is — Import-Module creates a child session state (a nested scope with its own function/variable/alias tables) and executes the module's script inside that child state, then selectively exports a subset of its members back into your caller's session as FunctionInfo/AliasInfo objects registered against the module. This is the actual mechanism behind module encapsulation: a helper function you don't Export- ModuleMember genuinely doesn't exist outside the module's own scope — it's not hidden by convention, it's unreachable because it lives in a session state your current scope has no reference to.

Dot-sourcing (. .\script.ps1) is the opposite: it runs the script's code directly in the caller's own current scope, no child session state created at all, so every variable and function the script defines lands straight into your session as if you'd typed it yourself — that's exactly why dot-sourced helper scripts can accidentally clobber your existing variables, while an imported module's internals can't.

Module discovery walks $env:PSModulePath, a semicolon/colon-separated list of directories, checking each for a subfolder matching the module name containing either a .psd1 manifest or a same-named .psm1/.dll. The manifest (.psd1) isn't code — it's a restricted-mode hashtable literal (RootModule, ModuleVersion, RequiredModules, FunctionsToExport, ...) that the module loader parses in a constrained-language context before deciding how to load the module, which is how PowerShell can validate compatibility (PowerShellVersion, CompatiblePSEditions) and pre-declare exports without ever executing the module's actual code.

🔀 See this in another language

Exercise

Create a StringHelpers.psm1 module with two functions — ConvertTo-TitleCaseWords (splits a sentence into words and title-cases each) and Get-WordCount (returns the number of words in a string) — export both with Export-ModuleMember, then write a separate test.ps1 that imports the module and calls both functions on a sample sentence.