PowerShell/04core11 min

Functions, modules and reuse

As the ZEUS probes grow, copying the same functions between scripts becomes a problem. The solution is modules — packages of functions with a clearly defined public API. This lesson shows how we build them.

From a script to a function

We extract a reusable piece of logic into a Verb-Noun function with [CmdletBinding()].

function ConvertTo-LsnResult {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] $Data,
        [switch]$AsJson
    )
    if ($AsJson) {
        $Data | ConvertTo-Json -Depth 6 -Compress
    } else {
        $Data
    }
}

Script module (.psm1)

We group related functions in a .psm1 module file. A module is imported once, and its functions are available like built-in cmdlets.

LsnProbe/
├── LsnProbe.psd1        # manifest (metadata, version, exports)
├── LsnProbe.psm1        # implementation
├── Public/              # exported functions
│   ├── Get-LsnForest.ps1
│   ├── Get-LsnInventory.ps1
│   └── Get-LsnPerf.ps1
└── Private/             # helper functions (not exported)
    └── Invoke-LsnLdapPaged.ps1

In .psm1 we load the files and explicitly export only the public functions:

# LsnProbe.psm1
$public  = Get-ChildItem "$PSScriptRoot/Public/*.ps1"
$private = Get-ChildItem "$PSScriptRoot/Private/*.ps1"

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

Export-ModuleMember -Function $public.BaseName

ProfessNet standard: we split a module into Public/ (the external API) and Private/ (helpers). We export only the public functions via Export-ModuleMember. Helpers like Invoke-LsnLdapPaged stay internal.

Manifest (.psd1)

The manifest describes the module: version, dependencies, which functions it exports.

@{
    RootModule        = 'LsnProbe.psm1'
    ModuleVersion     = '1.4.0'
    PowerShellVersion = '5.1'
    FunctionsToExport = @('Get-LsnForest', 'Get-LsnInventory', 'Get-LsnPerf')
    PrivateData       = @{ PSData = @{ Tags = @('ZEUS', 'AD', 'inventory') } }
}

ProfessNet standard: we list FunctionsToExport explicitly (not '*') — this speeds up loading and documents the public API. We bump the module version according to SemVer.

Comment-based help

We document public functions with a comment that feeds Get-Help.

function Get-LsnInventory {
    <#
    .SYNOPSIS
        Collects a host inventory from an AD forest.
    .PARAMETER Forest
        The forest name, e.g. corp.local.
    .EXAMPLE
        Get-LsnInventory -Forest corp.local -AsJson
    #>
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Forest, [switch]$AsJson)
    # ...
}
FileContains
*.psd1 (manifest)metadata, version, the export list
*.psm1 (module)loading files, Export-ModuleMember
Public/*.ps1API functions
Private/*.ps1helpers, not exported

Tip: test the module with Pester (the PowerShell test framework). Import the module in tests via Import-Module ./LsnProbe -Force and check the public functions.


Verb-Noun functions, the Public/Private split, a manifest with an explicit export and comment-based help — that's how we build reusable probe modules. One module instead of ten copies of the same logic.