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.
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:
.psm1is the file extension that identifies this file as a script module.function Get-DriveFreeSpacePercentis the function defined within the module scope, following the standard Verb-Noun naming convention.[CmdletBinding()]standardizes the function with common parameters like-Verboseand-ErrorAction.Get-Volumeis a cmdlet querying partition metrics.
Import-Module ./QuickOps.psm1
Get-DriveFreeSpacePercent -DriveLetter "C"DriveLetter FreePercent SizeGB FreeGB
----------- ----------- ------ ------
C 48.23 476.12 229.63A Multi-Function Example
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
}
}Import-Module -Name "C:\Scripts\Modules\MyTools\MyTools.psm1"
Get-UptimeReportComputerName LastBoot Uptime
------------ -------- ------
DESKTOP-01 5/10/2024 8:15:00 AM 03:58:02.4546554Controlling 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.
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-AuditTimestampwhere:
Format-AuditTimestampis an internal helper function. Because it is omitted fromExport-ModuleMember, callers cannot see or execute it directly.Get-SystemUptimeis a public operational function that consumes the internal helper.Export-ModuleMemberdirects PowerShell to expose only the specified functions, aliases, or variables.-Functionspecifies an array of function names to make available in the user session.
Import-Module ./SystemAudit.psm1 -Force
Get-SystemUptime
Format-AuditTimestamp -DateTime (Get-Date)UptimeDays UptimeHours LastBootTime CollectedTime
---------- ----------- ------------ -------------
0 4 2026-09-28 18:42:31 2026-09-28 22:44:02
2026-09-28 22:44:02Exporting Multiple Members
-Function accepts multiple names, and Export-ModuleMember also supports -Alias, -Variable, and -Cmdlet parameters.
Export-ModuleMember -Function Get-DiskSpaceReport, Get-UptimeReport -Alias gdswhere:
-Functionis a comma-separated list of function names to export.-Aliasis any aliases defined within the module (viaSet-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.
Recommended Directory Layout
📁 SystemOps/
├── 📁 Public/
│ ├── Get-DriveFreeSpacePercent.ps1
│ └── Get-SystemUptime.ps1
├── 📁 Private/
│ └── Format-AuditTimestamp.ps1
├── SystemOps.psd1
└── SystemOps.psm1Writing 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.
$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:
$PSScriptRootis an automatic variable containing the directory where the.psm1file is saved.Join-Pathconstructs cross-platform filesystem paths safely.. $file.FullNamedot-sources the target script, loading the function definition into the module's internal memory scope.$publicFiles.BaseNamereturns an array of file names without extensions (e.g.,Get-SystemUptime), matching the function names defined inside.
Import-Module ./SystemOps/SystemOps.psm1 -Force
Get-Command -Module SystemOpsCommandType Name Version Source
----------- ---- ------- ------
Function Get-DriveFreeSpacePercent 0.0 SystemOps
Function Get-SystemUptime 0.0 SystemOpsPractical Usage: A Complete Custom Module
Putting it all together, here's a minimal but complete module structure ready for distribution.
function Get-DiskSpaceReport {
<#
.SYNOPSIS
Returns a summary of disk space usage for all volumes.
Get-Volume | Select-Object DriveLetter, FileSystemLabel, SizeRemaining, Size
}function ConvertTo-Percentage {
param([double]$Value, [double]$Total)
[math]::Round(($Value / $Total) * 100, 2)
}$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.BaseNameImport-Module -Name "C:\Modules\MyTools" -Force
Get-DiskSpaceReportDriveLetter FileSystemLabel SizeRemaining Size
----------- --------------- ------------- ----
C Windows 128849018880 512110190592Practical Administration: Exporting Aliases and Shared Variables
Modules can provide built-in command shortcuts (aliases) and read-only environment variables along with functions.
$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 ModuleInitTimewhere:
$ModuleInitTimeis a variable defined inside the module.Set-AliasmapsgenvtoGet-EnvironmentReport.-Aliasis a parameter onExport-ModuleMemberdeclaring aliases exposed to callers.-Variableexposes module-scoped variables directly to the calling session.
Import-Module ./EnvTools.psm1 -Force
genv
Write-Output "Module initialized at: $ModuleInitTime"ComputerName UserName ModuleLoaded
------------ -------- ------------
SRV-PROD01 Admin 3/31/2026 10:48:22 AM
Module initialized at: 03/31/2026 10:48:22