What Are PowerShell Modules?

PowerShell modules are packages that contain cmdlets, functions, variables, and other resources organized into reusable components. They enable code organization, distribution, and reuse across scripts and environments. A module can be as simple as a single script file (.psm1) or a complex package with manifests (.psd1), nested modules, and type definitions.

Modules solve the problem of code duplication and provide a standardized way to share PowerShell functionality. Instead of copying functions between scripts, you import a module and access its exported commands instantly.

Understanding and Using Modules in PowerShell: Complete Guide to Manifest, PSM1, and PSD1 Files

Types of PowerShell Modules

Script Modules (PSM1)

Script modules are PowerShell script files with a .psm1 extension containing functions, workflows, and variables. These are the most common type for custom development and learning.

# MyUtilities.psm1
function Get-SystemUptime {
    [CmdletBinding()]
    param()
    
    $os = Get-CimInstance Win32_OperatingSystem
    $uptime = (Get-Date) - $os.LastBootUpTime
    
    [PSCustomObject]@{
        Days    = $uptime.Days
        Hours   = $uptime.Hours
        Minutes = $uptime.Minutes
        Total   = $uptime.ToString()
    }
}

function Get-DiskSpace {
    [CmdletBinding()]
    param(
        [string]$DriveLetter = "C:"
    )
    
    Get-PSDrive $DriveLetter.TrimEnd(':') | Select-Object Name, 
        @{N='UsedGB';E={[math]::Round($_.Used/1GB,2)}},
        @{N='FreeGB';E={[math]::Round($_.Free/1GB,2)}},
        @{N='TotalGB';E={[math]::Round(($_.Used+$_.Free)/1GB,2)}}
}

Export-ModuleMember -Function Get-SystemUptime, Get-DiskSpace

Module Manifests (PSD1)

A module manifest is a PowerShell data file (.psd1) containing metadata about the module including version, author, required modules, and what gets exported. Manifests are optional but strongly recommended for production modules.

Binary Modules (DLL)

Binary modules are compiled .NET assemblies containing cmdlets written in C# or other .NET languages. These offer better performance for computationally intensive operations.

Creating Your First Script Module

Basic Module Structure

Create a directory for your module and a .psm1 file with the same name:

# Create module directory
$modulePath = "$HOME\Documents\PowerShell\Modules\DataProcessor"
New-Item -ItemType Directory -Path $modulePath -Force

# Create the module file
$moduleContent = @'
function ConvertTo-Uppercase {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string]$Text
    )
    
    process {
        $Text.ToUpper()
    }
}

function Get-StringStats {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Text
    )
    
    [PSCustomObject]@{
        Length = $Text.Length
        Words = ($Text -split '\s+').Count
        Vowels = ([regex]::Matches($Text, '[aeiouAEIOU]')).Count
        Consonants = ([regex]::Matches($Text, '[bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ]')).Count
    }
}

Export-ModuleMember -Function ConvertTo-Uppercase, Get-StringStats
'@

Set-Content -Path "$modulePath\DataProcessor.psm1" -Value $moduleContent

Output:

Directory: C:\Users\YourName\Documents\PowerShell\Modules

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10/22/2025   3:04 PM                DataProcessor

Using the Module

# Import the module
Import-Module DataProcessor

# Use the functions
"hello world" | ConvertTo-Uppercase

Get-StringStats -Text "PowerShell modules are powerful"

Output:

HELLO WORLD

Length     : 32
Words      : 4
Vowels     : 11
Consonants : 18

Understanding Module Manifests (PSD1)

Module manifests provide structure and metadata for your modules. They control versioning, dependencies, and exports while documenting authorship and compatibility.

Understanding and Using Modules in PowerShell: Complete Guide to Manifest, PSM1, and PSD1 Files

Creating a Module Manifest

# Generate manifest for DataProcessor module
$manifestParams = @{
    Path = "$HOME\Documents\PowerShell\Modules\DataProcessor\DataProcessor.psd1"
    RootModule = 'DataProcessor.psm1'
    ModuleVersion = '1.0.0'
    Author = 'CodeLucky Team'
    CompanyName = 'CodeLucky.com'
    Description = 'Data processing utilities for text manipulation'
    PowerShellVersion = '5.1'
    FunctionsToExport = @('ConvertTo-Uppercase', 'Get-StringStats')
    CmdletsToExport = @()
    VariablesToExport = @()
    AliasesToExport = @()
    Tags = @('Data', 'Text', 'Utility')
    ProjectUri = 'https://codelucky.com/modules/dataprocessor'
}

New-ModuleManifest @manifestParams

Manifest File Structure

# DataProcessor.psd1 (generated content)
@{
    RootModule = 'DataProcessor.psm1'
    ModuleVersion = '1.0.0'
    GUID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
    Author = 'CodeLucky Team'
    CompanyName = 'CodeLucky.com'
    Copyright = '(c) 2025 CodeLucky.com. All rights reserved.'
    Description = 'Data processing utilities for text manipulation'
    
    PowerShellVersion = '5.1'
    
    FunctionsToExport = @('ConvertTo-Uppercase', 'Get-StringStats')
    CmdletsToExport = @()
    VariablesToExport = @()
    AliasesToExport = @()
    
    PrivateData = @{
        PSData = @{
            Tags = @('Data', 'Text', 'Utility')
            ProjectUri = 'https://codelucky.com/modules/dataprocessor'
        }
    }
}

Advanced Module Development

Module with Multiple Files

Organize larger modules with separate files for different function categories:

# Create organized module structure
$moduleName = "NetworkTools"
$modulePath = "$HOME\Documents\PowerShell\Modules\$moduleName"

New-Item -ItemType Directory -Path "$modulePath\Public" -Force
New-Item -ItemType Directory -Path "$modulePath\Private" -Force

# Public\Get-PublicIP.ps1
$publicFunction = @'
function Get-PublicIP {
    [CmdletBinding()]
    param()
    
    try {
        $response = Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' -TimeoutSec 5
        [PSCustomObject]@{
            IPAddress = $response.ip
            TimeStamp = Get-Date
        }
    }
    catch {
        Write-Error "Failed to retrieve public IP: $_"
    }
}
'@

Set-Content -Path "$modulePath\Public\Get-PublicIP.ps1" -Value $publicFunction

# Private\Write-Log.ps1
$privateFunction = @'
function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    Write-Verbose "[$timestamp] $Message"
}
'@

Set-Content -Path "$modulePath\Private\Write-Log.ps1" -Value $privateFunction

# NetworkTools.psm1 (root module)
$rootModule = @'
# Import private functions
Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 | ForEach-Object {
    . $_.FullName
}

# Import public functions
Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 | ForEach-Object {
    . $_.FullName
}

# Export only public functions
Export-ModuleMember -Function (Get-ChildItem -Path $PSScriptRoot\Public\*.ps1).BaseName
'@

Set-Content -Path "$modulePath\$moduleName.psm1" -Value $rootModule

Understanding and Using Modules in PowerShell: Complete Guide to Manifest, PSM1, and PSD1 Files

Module Dependencies

Specify required modules in your manifest to ensure dependencies are available:

# Creating a manifest with dependencies
$manifestParams = @{
    Path = "$modulePath\NetworkTools.psd1"
    RootModule = 'NetworkTools.psm1'
    ModuleVersion = '1.0.0'
    RequiredModules = @(
        @{ModuleName='Microsoft.PowerShell.Management'; ModuleVersion='3.0.0.0'}
    )
    FunctionsToExport = @('Get-PublicIP')
}

New-ModuleManifest @manifestParams

Module Discovery and Installation

Module Paths

PowerShell searches for modules in directories specified by $env:PSModulePath:

# View module paths
$env:PSModulePath -split ';'

# Add custom path (temporary - current session)
$env:PSModulePath += ";C:\CustomModules"

# Add custom path (permanent)
[Environment]::SetEnvironmentVariable(
    'PSModulePath',
    $env:PSModulePath + ";C:\CustomModules",
    'User'
)

Output:

C:\Users\YourName\Documents\PowerShell\Modules
C:\Program Files\PowerShell\Modules
C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules

Listing and Finding Modules

# List all available modules
Get-Module -ListAvailable

# Find specific module
Get-Module -ListAvailable -Name DataProcessor

# View module details
Get-Module DataProcessor | Format-List *

# Check if module is imported
Get-Module DataProcessor

Output:

    Directory: C:\Users\YourName\Documents\PowerShell\Modules

ModuleType Version    Name                ExportedCommands
---------- -------    ----                ----------------
Script     1.0.0      DataProcessor       {ConvertTo-Uppercase, Get-StringStats}

Export Control and Visibility

Explicit Exports in PSM1

# Module with explicit exports
function Public-Function {
    Write-Output "This is exported"
}

function Private-Helper {
    Write-Output "This is internal"
}

# Only export what's needed
Export-ModuleMember -Function Public-Function

# Export functions and aliases
Export-ModuleMember -Function Public-Function -Alias pf

Manifest-Level Export Control

# In the .psd1 manifest
@{
    FunctionsToExport = @('Get-Data', 'Set-Data')  # Specific functions
    CmdletsToExport = @()                          # No cmdlets
    VariablesToExport = @()                        # No variables
    AliasesToExport = @('gd', 'sd')               # Specific aliases
}

Understanding and Using Modules in PowerShell: Complete Guide to Manifest, PSM1, and PSD1 Files

Working with Module Commands

Importing Modules

# Import with auto-loading (PowerShell 3.0+)
Get-SystemUptime  # Automatically imports module if in PSModulePath

# Explicit import
Import-Module DataProcessor

# Import with prefix to avoid naming conflicts
Import-Module DataProcessor -Prefix DP
Get-DPSystemUptime  # Function now has DP prefix

# Import specific version
Import-Module DataProcessor -RequiredVersion 1.0.0

# Force reimport (refresh)
Import-Module DataProcessor -Force

Removing Modules

# Remove module from current session
Remove-Module DataProcessor

# Remove all versions
Get-Module DataProcessor | Remove-Module

# Verify removal
Get-Module DataProcessor  # Returns nothing

Module Versioning Best Practices

Semantic Versioning

Follow semantic versioning (MAJOR.MINOR.PATCH) in your manifest:

# Update manifest version
Update-ModuleManifest -Path ".\DataProcessor.psd1" -ModuleVersion "1.1.0"

# Version breakdown:
# 1.0.0 - Initial release
# 1.1.0 - Added new function (minor)
# 1.1.1 - Bug fix (patch)
# 2.0.0 - Breaking changes (major)

Managing Multiple Versions

# Install multiple versions
$modulePath = "$HOME\Documents\PowerShell\Modules\DataProcessor"
New-Item -ItemType Directory -Path "$modulePath\1.0.0" -Force
New-Item -ItemType Directory -Path "$modulePath\1.1.0" -Force

# List all versions
Get-Module DataProcessor -ListAvailable | Select-Object Name, Version, Path

# Import specific version
Import-Module DataProcessor -RequiredVersion 1.0.0

Testing and Validation

Test Module Manifest

# Validate manifest syntax
Test-ModuleManifest -Path ".\DataProcessor.psd1"

# Check for errors
$manifest = Test-ModuleManifest -Path ".\DataProcessor.psd1"
$manifest | Format-List Name, Version, ExportedFunctions

Output:

Name              : DataProcessor
Version           : 1.0.0
ExportedFunctions : {ConvertTo-Uppercase, Get-StringStats}

Module Testing Template

# Create test script
$testScript = @'
Import-Module DataProcessor -Force

# Test 1: Function exists
$function = Get-Command ConvertTo-Uppercase -ErrorAction SilentlyContinue
if ($function) {
    Write-Host "✓ ConvertTo-Uppercase exists" -ForegroundColor Green
} else {
    Write-Host "✗ ConvertTo-Uppercase not found" -ForegroundColor Red
}

# Test 2: Function works correctly
$result = "test" | ConvertTo-Uppercase
if ($result -eq "TEST") {
    Write-Host "✓ ConvertTo-Uppercase works correctly" -ForegroundColor Green
} else {
    Write-Host "✗ ConvertTo-Uppercase failed" -ForegroundColor Red
}

# Test 3: Get-StringStats accuracy
$stats = Get-StringStats -Text "Hello"
if ($stats.Length -eq 5 -and $stats.Vowels -eq 2) {
    Write-Host "✓ Get-StringStats accurate" -ForegroundColor Green
} else {
    Write-Host "✗ Get-StringStats inaccurate" -ForegroundColor Red
}

Remove-Module DataProcessor
'@

Invoke-Expression $testScript

Advanced Manifest Features

Nested Modules

# Manifest with nested modules
@{
    RootModule = 'MainModule.psm1'
    ModuleVersion = '1.0.0'
    
    # Additional modules to load
    NestedModules = @(
        'Helpers\StringHelpers.psm1',
        'Helpers\FileHelpers.psm1'
    )
    
    FunctionsToExport = @('*')  # Export all from all modules
}

Scripts to Process

# Run initialization scripts on import
@{
    RootModule = 'MyModule.psm1'
    ModuleVersion = '1.0.0'
    
    # Scripts run before module loads
    ScriptsToProcess = @('Initialize.ps1')
}

# Initialize.ps1 example
Write-Verbose "Initializing MyModule..."
$script:ModuleConfig = @{
    CachePath = "$env:TEMP\MyModule"
    LogLevel = 'Info'
}

Real-World Module Example

Complete FileManager Module

# FileManager.psm1
function Get-LargeFiles {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Path,
        
        [int]$MinSizeMB = 100
    )
    
    if (-not (Test-Path $Path)) {
        throw "Path not found: $Path"
    }
    
    Get-ChildItem -Path $Path -File -Recurse -ErrorAction SilentlyContinue |
        Where-Object { $_.Length -gt ($MinSizeMB * 1MB) } |
        Select-Object Name, Directory, 
            @{N='SizeMB';E={[math]::Round($_.Length/1MB,2)}},
            LastWriteTime |
        Sort-Object SizeMB -Descending
}

function Remove-OldFiles {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [string]$Path,
        
        [int]$DaysOld = 30
    )
    
    $cutoffDate = (Get-Date).AddDays(-$DaysOld)
    $files = Get-ChildItem -Path $Path -File -Recurse |
        Where-Object { $_.LastWriteTime -lt $cutoffDate }
    
    foreach ($file in $files) {
        if ($PSCmdlet.ShouldProcess($file.FullName, "Delete")) {
            Remove-Item $file.FullName -Force
            Write-Verbose "Deleted: $($file.FullName)"
        }
    }
    
    [PSCustomObject]@{
        FilesProcessed = $files.Count
        CutoffDate = $cutoffDate
    }
}

function Get-DirectorySize {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string]$Path
    )
    
    process {
        if (Test-Path $Path) {
            $size = Get-ChildItem -Path $Path -Recurse -File -ErrorAction SilentlyContinue |
                Measure-Object -Property Length -Sum
            
            [PSCustomObject]@{
                Path = $Path
                SizeMB = [math]::Round($size.Sum/1MB,2)
                SizeGB = [math]::Round($size.Sum/1GB,2)
                FileCount = $size.Count
            }
        }
    }
}

Export-ModuleMember -Function Get-LargeFiles, Remove-OldFiles, Get-DirectorySize

FileManager Manifest

# FileManager.psd1
@{
    RootModule = 'FileManager.psm1'
    ModuleVersion = '1.2.0'
    GUID = 'f9e7d6c5-b4a3-4928-9e7f-8d6c5b4a3928'
    Author = 'CodeLucky Team'
    CompanyName = 'CodeLucky.com'
    Copyright = '(c) 2025 CodeLucky.com'
    Description = 'File management utilities for PowerShell'
    
    PowerShellVersion = '5.1'
    
    FunctionsToExport = @(
        'Get-LargeFiles',
        'Remove-OldFiles',
        'Get-DirectorySize'
    )