Skip to content

Creating PowerShell Modules

How to package PowerShell scripts, functions, and variables into reusable modules with .psm1 files, control visibility using Export-ModuleMember, and organize multi-file layouts for maintainable automation tools.

A custom PowerShell module packages standalone scripts, functions, aliases, and variables into a reusable, shareable toolset. Creating a module involves writing a script module file (.psm1), organizing functions into public and private scopes, controlling command visibility with Export-ModuleMember, and structuring larger modules into maintainable multi-file layouts.

Writing a Basic Script Module (.psm1)

A script module is a text file containing valid PowerShell code saved with the .psm1 extension. Any functions defined inside this file can be imported into a session as a consolidated command library.

QuickOps.psm1powershell
function Get-DriveFreeSpacePercent {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$DriveLetter
    )

    $drive = Get-Volume -DriveLetter $DriveLetter
    $percent = [math]::Round(($drive.SizeRemaining / $drive.Size) * 100, 2)
    
    [PSCustomObject]@{
        DriveLetter = $DriveLetter.ToUpper()
        FreePercent = $percent
        SizeGB      = [math]::Round($drive.Size / 1GB, 2)
        FreeGB      = [math]::Round($drive.SizeRemaining / 1GB, 2)
    }
}

where:

  • .psm1 is the file extension that identifies this file as a script module.
  • function Get-DriveFreeSpacePercent is the function defined within the module scope, following the standard Verb-Noun naming convention.
  • [CmdletBinding()] standardizes the function with common parameters like -Verbose and -ErrorAction.
  • Get-Volume is a cmdlet querying partition metrics.
Consolepowershell
Import-Module ./QuickOps.psm1
Get-DriveFreeSpacePercent -DriveLetter "C"
Outputplaintext
DriveLetter FreePercent SizeGB FreeGB
----------- ----------- ------ ------
C                 48.23 476.12 229.63

A Multi-Function Example

MyTools.psm1powershell
function Get-DiskSpaceReport {
    Get-Volume | Select-Object DriveLetter, FileSystemLabel, SizeRemaining, Size
}

function Get-UptimeReport {
    $os = Get-CimInstance -ClassName Win32_OperatingSystem
    $uptime = (Get-Date) - $os.LastBootUpTime
    [PSCustomObject]@{
        ComputerName = $env:COMPUTERNAME
        LastBoot     = $os.LastBootUpTime
        Uptime       = $uptime
    }
}
Consolepowershell
Import-Module -Name "C:\Scripts\Modules\MyTools\MyTools.psm1"
Get-UptimeReport
Outputplaintext
ComputerName LastBoot              Uptime
------------ --------              ------
DESKTOP-01   5/10/2024 8:15:00 AM  03:58:02.4546554

Controlling Visibility with Export-ModuleMember

By default, all functions defined in a .psm1 file are exported and become available after import. In real-world modules, you typically write internal helper functions that support primary tasks but should not be exposed to the end user. Use Export-ModuleMember to explicitly control which functions, aliases, or variables are exposed.

SystemAudit.psm1powershell
function Format-AuditTimestamp {
    param([datetime]$DateTime)

    return $DateTime.ToString("yyyy-MM-dd HH:mm:ss")
}

function Get-SystemUptime {
    [CmdletBinding()]
    param()

    $os = Get-CimInstance -ClassName Win32_OperatingSystem
    $uptime = (Get-Date) - $os.LastBootUpTime

    [PSCustomObject]@{
        UptimeDays     = $uptime.Days
        UptimeHours    = $uptime.Hours
        LastBootTime   = Format-AuditTimestamp -DateTime $os.LastBootUpTime
        CollectedTime  = Format-AuditTimestamp -DateTime (Get-Date)
    }
}

Export-ModuleMember -Function Get-SystemUptime, Format-AuditTimestamp

where:

  • Format-AuditTimestamp is an internal helper function. Because it is omitted from Export-ModuleMember, callers cannot see or execute it directly.
  • Get-SystemUptime is a public operational function that consumes the internal helper.
  • Export-ModuleMember directs PowerShell to expose only the specified functions, aliases, or variables.
  • -Function specifies an array of function names to make available in the user session.
Consolepowershell
Import-Module ./SystemAudit.psm1 -Force
Get-SystemUptime
Format-AuditTimestamp -DateTime (Get-Date)
Outputplaintext
UptimeDays UptimeHours LastBootTime        CollectedTime
---------- ----------- ------------        -------------
         0           4 2026-09-28 18:42:31 2026-09-28 22:44:02
2026-09-28 22:44:02

Exporting Multiple Members

-Function accepts multiple names, and Export-ModuleMember also supports -Alias, -Variable, and -Cmdlet parameters.

Consolepowershell
Export-ModuleMember -Function Get-DiskSpaceReport, Get-UptimeReport -Alias gds

where:

  • -Function is a comma-separated list of function names to export.
  • -Alias is any aliases defined within the module (via Set-Alias) to also expose.

Organizing Multi-File Modules

As a module grows, placing hundreds of functions into a single .psm1 file quickly creates an unmaintainable codebase. Professional PowerShell modules use a modular directory structure, splitting each function into its own .ps1 file within Public/ and Private/ directories, and dynamically loading them through a central .psm1 loader file.

📁 SystemOps/
├── 📁 Public/
│   ├── Get-DriveFreeSpacePercent.ps1
│   └── Get-SystemUptime.ps1
├── 📁 Private/
│   └── Format-AuditTimestamp.ps1
├── SystemOps.psd1
└── SystemOps.psm1

Writing the Module Loader (.psm1)

The root .psm1 file dot-sources each individual script file into the module's scope and exports only the functions located in the Public/ directory.

SystemOps.psm1powershell
$moduleRoot = $PSScriptRoot

$privateFiles = Get-ChildItem -Path (Join-Path $moduleRoot "Private") -Filter "*.ps1" -ErrorAction SilentlyContinue
foreach ($file in $privateFiles) {
    . $file.FullName
}

$publicFiles = Get-ChildItem -Path (Join-Path $moduleRoot "Public") -Filter "*.ps1" -ErrorAction SilentlyContinue
foreach ($file in $publicFiles) {
    . $file.FullName
}

if ($publicFiles) {
    Export-ModuleMember -Function $publicFiles.BaseName
}

where:

  • $PSScriptRoot is an automatic variable containing the directory where the .psm1 file is saved.
  • Join-Path constructs cross-platform filesystem paths safely.
  • . $file.FullName dot-sources the target script, loading the function definition into the module's internal memory scope.
  • $publicFiles.BaseName returns an array of file names without extensions (e.g., Get-SystemUptime), matching the function names defined inside.
Consolepowershell
Import-Module ./SystemOps/SystemOps.psm1 -Force
Get-Command -Module SystemOps
Outputplaintext
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Get-DriveFreeSpacePercent                          0.0        SystemOps
Function        Get-SystemUptime                                   0.0        SystemOps

Practical Usage: A Complete Custom Module

Putting it all together, here's a minimal but complete module structure ready for distribution.

Public/Get-DiskSpaceReport.ps1powershell
function Get-DiskSpaceReport {
    <#
    .SYNOPSIS
    Returns a summary of disk space usage for all volumes.
    Get-Volume | Select-Object DriveLetter, FileSystemLabel, SizeRemaining, Size
}
Private/ConvertTo-Percentage.ps1powershell
function ConvertTo-Percentage {
    param([double]$Value, [double]$Total)
    [math]::Round(($Value / $Total) * 100, 2)
}
MyTools.psm1powershell
$public  = Get-ChildItem -Path "$PSScriptRoot\Public\*.ps1"
$private = Get-ChildItem -Path "$PSScriptRoot\Private\*.ps1"

foreach ($file in @($public + $private)) {
    . $file.FullName
}

Export-ModuleMember -Function $public.BaseName
Consolepowershell
Import-Module -Name "C:\Modules\MyTools" -Force
Get-DiskSpaceReport
Outputplaintext
DriveLetter FileSystemLabel SizeRemaining        Size
----------- --------------- -------------        ----
C           Windows          128849018880 512110190592

Practical Administration: Exporting Aliases and Shared Variables

Modules can provide built-in command shortcuts (aliases) and read-only environment variables along with functions.

EnvTools.psm1powershell
$ModuleInitTime = Get-Date

function Get-EnvironmentReport {
    [CmdletBinding()]
    param()

    [PSCustomObject]@{
        ComputerName = $env:COMPUTERNAME
        UserName     = $env:USERNAME
        ModuleLoaded = $ModuleInitTime
    }
}

Set-Alias -Name "genv" -Value Get-EnvironmentReport

Export-ModuleMember -Function Get-EnvironmentReport -Alias "genv" -Variable ModuleInitTime

where:

  • $ModuleInitTime is a variable defined inside the module.
  • Set-Alias maps genv to Get-EnvironmentReport.
  • -Alias is a parameter on Export-ModuleMember declaring aliases exposed to callers.
  • -Variable exposes module-scoped variables directly to the calling session.
Consolepowershell
Import-Module ./EnvTools.psm1 -Force
genv
Write-Output "Module initialized at: $ModuleInitTime"
Outputplaintext
ComputerName UserName ModuleLoaded
------------ -------- ------------
SRV-PROD01   Admin    3/31/2026 10:48:22 AM

Module initialized at: 03/31/2026 10:48:22
Navigation