# Your Own Module

A module is a folder of functions that PowerShell loads by name: the functions go in a .psm1 file, Export-ModuleMember chooses which ones others can call, New-ModuleManifest writes a .psd1 that gives the module a version and a description, and a folder on $env:PSModulePath lets PowerShell find it from anywhere.

Journeyman · PowerShell · Updated November 10, 2026

## After this lesson you can

- Turn a file of functions into a module with a .psm1 file, Import-Module and Export-ModuleMember
- Give a module a manifest with New-ModuleManifest, and put it where PowerShell finds it by name through $env:PSModulePath

## Key takeaways

- A module is a folder with a .psm1 file of functions in it, both named after the module. Import-Module loads it.
- Export-ModuleMember -Function lists the commands others can call. Helpers that aren't listed stay inside the module.
- New-ModuleManifest writes a .psd1 file, the module's label: its version, author, description and the commands it shares.
- $env:PSModulePath lists the folders where PowerShell looks for modules. A module there loads by itself the first time you use one of its commands.
- After you change a module, Import-Module -Force loads the new version.

Your playlist functions work, and now they live in a script you dot-source, or pasted at the bottom of your profile, which grows longer every week. A friend asks for "your playlist commands", and you send a file with a note: "dot-source this, and ignore the helper functions, they're not for you." PowerShell's own commands don't arrive like that. You type `Compress-Archive` and it's there, with a version number, and nobody tells you about its helpers. They come in **modules**, and yours can too. IT teams hand out their tools as modules, and so does everyone who publishes to the PowerShell Gallery.

Make a practice folder with a playlist to work on:

```powershell
New-Item -ItemType Directory -Force -Path "$HOME\terminal-practice\modules\PlaylistTools", "$HOME\terminal-practice\playlists" | Out-Null
Set-Content -Path "$HOME\terminal-practice\playlists\road-trip.csv" -Value 'Title,Artist,Length', 'Golden Hour Drive,The Night Owls,3:45', 'Rain on the Roof,Clara Moss,4:15', 'Long Way Home,Harbor Lights,5:30'
Set-Location "$HOME\terminal-practice\modules"
```

## From a script to a module: a .psm1 file

A module is a toolbox with a label on it. In its simplest form it's a folder, and inside it a file of functions with the extension `.psm1` (PowerShell script module), both with the module's name. Open the folder in VS Code (`code PlaylistTools`) and save this as `PlaylistTools.psm1`. It's a short set of playlist functions, cousins of the ones you wrote in *Functions: Naming a Job*: this `Get-Song` has the same name, but it lives in a new file, for a playlist that keeps each song's length as minutes and seconds. `Get-Song` reads the file into objects, `Measure-Playlist` adds up the minutes, and `ConvertTo-Second` is a helper that turns `3:45` into 225 seconds. Your own `PlaylistTools.ps1` becomes a module the same way.

```powershell
# PlaylistTools.psm1: tools for playlists saved as CSV files.

function ConvertTo-Second {
    param([string] $Length)
    $parts = $Length -split ':'
    [int] $parts[0] * 60 + [int] $parts[1]
}

function Get-Song {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $Path
    )
    foreach ($row in Import-Csv -Path $Path) {
        [PSCustomObject]@{
            Title   = $row.Title
            Artist  = $row.Artist
            Seconds = ConvertTo-Second -Length $row.Length
        }
    }
}

function Measure-Playlist {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [PSCustomObject] $Song
    )
    begin   { $total = 0; $count = 0 }
    process { $total += $Song.Seconds; $count++ }
    end {
        [PSCustomObject]@{
            Songs   = $count
            Minutes = [math]::Round($total / 60, 1)
        }
    }
}
```

`Import-Module` loads it, from the folder's path, and `Get-Command -Module` lists what it brought:

```powershell
Import-Module .\PlaylistTools
Get-Command -Module PlaylistTools
Get-Song -Path ..\playlists\road-trip.csv | Measure-Playlist
```

```text
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        ConvertTo-Second                                   0.0        PlaylistTools
Function        Get-Song                                           0.0        PlaylistTools
Function        Measure-Playlist                                   0.0        PlaylistTools

Songs Minutes
----- -------
    3   13.50
```

## Share only what others need: \`Export-ModuleMember\`

`ConvertTo-Second` is in the list, so anyone who loads the module can call it, and may start to depend on it. A helper is a tool you keep inside the box. Add this line at the end of `PlaylistTools.psm1`, and save:

```powershell
Export-ModuleMember -Function Get-Song, Measure-Playlist
```

To **export** a command is to hand it out. A module that never says which commands to export hands out all its functions; once it does, only those listed leave the box. PowerShell keeps the old version loaded until you ask for the new one, so load it again with `-Force`:

```powershell
Import-Module .\PlaylistTools -Force
Get-Command -Module PlaylistTools
ConvertTo-Second -Length 3:45
```

```text
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Get-Song                                           0.0        PlaylistTools
Function        Measure-Playlist                                   0.0        PlaylistTools

ConvertTo-Second: The term 'ConvertTo-Second' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
```

`Get-Song` still calls `ConvertTo-Second` from inside the module. Only the prompt can't see it.

## A label for the box: the manifest

The Version column says `0.0`: the module has no label yet. A **module manifest** is a `.psd1` file beside the `.psm1` that says what the module is: its version, who wrote it, what it does, and which file holds its commands. `New-ModuleManifest` writes one, and `Test-ModuleManifest` checks it:

```powershell
New-ModuleManifest -Path .\PlaylistTools\PlaylistTools.psd1 -RootModule PlaylistTools.psm1 -ModuleVersion 1.0.0 -Author 'Sam' -Description 'Tools for playlists saved as CSV files' -FunctionsToExport Get-Song, Measure-Playlist
Test-ModuleManifest -Path .\PlaylistTools\PlaylistTools.psd1
```

```text
ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     1.0.0                 PlaylistTools                       {Get-Song, Measure-Playlist}
```

Open the `.psd1`: it's a long list, mostly comments for settings you don't need yet. The lines that matter now:

```powershell
RootModule = 'PlaylistTools.psm1'
ModuleVersion = '1.0.0'
Author = 'Sam'
Description = 'Tools for playlists saved as CSV files'
FunctionsToExport = 'Get-Song', 'Measure-Playlist'
```

`RootModule` names the file with the commands; without it, the module loads nothing. `FunctionsToExport` is the manifest's list of what to hand out. A command leaves the box only when it's on both lists, this one and `Export-ModuleMember`'s, so a new function has to be added to both. When you change the module, raise `ModuleVersion` (1.0.1 for a fix, 1.1.0 for a new command), so people can tell which copy they have.

![The PlaylistTools module drawn as a toolbox: the box is the PlaylistTools folder, the tag on its handle is PlaylistTools.psd1 with version 1.0.0, and inside is PlaylistTools.psm1 with three tools; Get-Song and Measure-Playlist are handed out to the prompt, while the helper ConvertTo-Second stays inside the box](https://fsdtrainingmedia.blob.core.windows.net/public/images/2026/11/f506d2c05b0643dcbd2f9127e827d858.webp)

Your recipe box from *Exercise: A Recipe Box Module* becomes a module the same way: a `RecipeBox` folder, `RecipeBox.psm1` and a manifest.

## Where PowerShell looks: \`$env:PSModulePath\`

You never typed `Import-Module` for `Compress-Archive`. **`$env:PSModulePath`** is the list of folders where PowerShell looks for modules, the way `PATH` lists the folders for programs:

```powershell
$env:PSModulePath -split ';'
```

```text
C:\Users\Sam\Documents\PowerShell\Modules
C:\Program Files\PowerShell\Modules
c:\program files\powershell\7\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
```

The first folder is yours, for modules you install for your account only. (If your Documents folder is kept in OneDrive, that path is inside OneDrive.) A module in one of these folders **loads by itself** the first time you use one of its commands. Try it with the practice folder, added to the front of the list for this window only:

```powershell
$env:PSModulePath = "$HOME\terminal-practice\modules;" + $env:PSModulePath
Remove-Module PlaylistTools
Set-Location $HOME
Get-Song -Path .\terminal-practice\playlists\road-trip.csv | Measure-Playlist
```

```text
Songs Minutes
----- -------
    3   13.50
```

No `Import-Module`, from your home folder: PowerShell found `Get-Song` in `PlaylistTools` on its own. To keep the module for good, copy its folder into your own modules folder, the first one in the list; a new window then finds it too. These lines make that folder if it isn't there yet, find your Documents folder wherever Windows keeps it, and are safe to run again after each change:

```powershell
$myModules = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'PowerShell\Modules'
New-Item -ItemType Directory -Force -Path $myModules | Out-Null
Copy-Item -Path "$HOME\terminal-practice\modules\PlaylistTools" -Destination $myModules -Recurse -Force
```

On a Mac or Linux, your modules folder is `~/.local/share/powershell/Modules`, and `$env:PSModulePath` separates its folders with `:` instead of `;`. Windows PowerShell 5.1 looks in `Documents\WindowsPowerShell\Modules` instead.

## Check yourself

2 questions to try on the lesson page.

## So what

A module turns a pile of functions into a tool with a name, a version and a clear front door, one that loads itself wherever you are and that you can hand to anyone.

## Key terms

- [Module](https://www.ask-codey.com/glossary/module-powershell)
- [Import-Module](https://www.ask-codey.com/glossary/import-module)
- [Export-ModuleMember](https://www.ask-codey.com/glossary/export-modulemember)
- [Module Manifest](https://www.ask-codey.com/glossary/module-manifest)
- [PSModulePath](https://www.ask-codey.com/glossary/psmodulepath)
- [Module Autoloading](https://www.ask-codey.com/glossary/module-autoloading)

## Cheat sheets

- [Your Own Module](https://www.ask-codey.com/cheat-sheets/powershell-modules-cheat-sheet)

## Related

- [Installing Apps and Modules: winget and the PowerShell Gallery](https://www.ask-codey.com/lessons/powershell-installing-tools)
- [Your Profile](https://www.ask-codey.com/lessons/powershell-profile)
- [Functions: Naming a Job](https://www.ask-codey.com/lessons/powershell-functions)
- [Where a Tool Keeps Its Data](https://www.ask-codey.com/lessons/powershell-app-data)

Source: https://www.ask-codey.com/lessons/powershell-modules
