Keeping Track of PowerShell Functions in Script Modules when Dot-Sourcing PS1 Files

I’m picking up where I left off in a previous blog article Write Dynamic Unit Tests for your PowerShell Code with Pester. I’m using the dynamic Test-MrFunctionsToExport Pester test that I wrote about in that previous blog article. Currently, the test is failing for my MrToolkit module that’s part of my PowerShell repository on GitHub:

Test-MrFunctionsToExport -ManifestPath .\GitHub\PowerShell\MrToolkit\MrToolkit.psd1

functionstoexport1a.jpg

Based on the previous results, I can easily determine that more functions exist in the module folder than are specified in the FunctionsToExport section of the module manifest. That may sound like a simple fix but once you have tons of PS1 files in the module folder and a similar number specified in the manifest, it becomes difficult to know what’s missing so I’ve written a tool to generate a list of the function names:

function Get-MrFunctionsToExport {

<#
.SYNOPSIS
    Returns a list of functions in the specified directory.

.DESCRIPTION
    Get-MrFunctionsToExport is an advanced function which returns a list of functions
    that are each contained in single quotes and each separated by a comma unless the
    simple parameter is specified in which case a simple list of the base file names
    for the functions is returned.

.PARAMETER Path
    Path to the folder where the functions are located.

.PARAMETER Exclude
    Pattern to exclude. By default profile scripts and Pester tests are excluded.

.PARAMETER Recurse
    Return function names from subdirectories in addition to the specified directory.

.PARAMETER Simple
    Return a simple list instead of a quoted comma separated list.

.EXAMPLE
    Get-MrFunctionsToExport -Path .\MrToolkit

.EXAMPLE
    Get-MrFunctionsToExport -Path .\MrToolkit -Simple

.INPUTS
    None

.OUTPUTS
    String

.NOTES
    Author:  Mike F Robbins
    Website: http://mikefrobbins.com
    Twitter: @mikefrobbins
#>

    [CmdletBinding()]
    param (
        [ValidateScript({
          If (Test-Path -Path $_ -PathType Container) {
            $True
          }
          else {
            Throw "'$_' is not a valid directory."
          }
        })]
        [string]$Path = (Get-Location),

        [string[]]$Exclude = ('*profile.ps1', '*.tests.ps1'),

        [switch]$Recurse,

        [switch]$Simple
    )

    $Params = @{
        Exclude = $Exclude
    }

    if ($PSBoundParameters.Recurse) {
        $Params.Recurse = $true
    }

    $results = Get-ChildItem -Path "$Path\*.ps1" @Params |
               Select-Object -ExpandProperty BaseName

    if ((-not($PSBoundParameters.Simple)) -and $results) {
        $results = $results -join "', '"
        Write-Output "'$results'"
    }
    elseif ($results) {
        Write-Output $results
    }

}

For those of you who didn’t read the previous blog article that I referenced, I’ve moved to the model of placing each of my functions into a separate PS1 file and the file base name is the same as the function name. Each PS1 file is dot-sourced in the PSM1 file when the module is imported and a list of the function names must exist in the FunctionsToExport section of the module manifest (or exported via Export-ModuleMember in the PSM1 file).

I’ll use this tool to generate a list of function names. If nothing is specified in the FormatsToProcess or TypesToProcess section of the module manifest, it can be updated programmatically using the Update-ModuleManifest cmdlet. Unfortunately a known error is generated as shown in the following example if either or both of these are specified:

Update-ModuleManifest -Path .\GitHub\PowerShell\MrToolkit\MrToolkit.psd1 -FunctionsToExport (Get-MrFunctionsToExport -Path .\GitHub\PowerShell\MrToolkit\ -Simple)

functionstoexport2a.jpg

Update-ModuleManifest : Cannot update the manifest file ‘.\GitHub\PowerShell\MrToolkit\MrToolkit.psd1’ because the
manifest is not valid. Verify that the manifest file is valid, and then try again.’The member ‘FormatsToProcess’ in
the module manifest is not valid: Cannot find path
‘C:\Users\mikefrobbins\AppData\Local\Temp\1707008812\GitHub\PowerShell\MrToolkit\MrToolkit.ps1xml’ because it does not
exist.. Verify that a valid value is specified for this field in the
‘C:\Users\mikefrobbins\AppData\Local\Temp\1707008812\NewManifest.psd1′ file.’
At line:1 char:1
+ Update-ModuleManifest -Path .\GitHub\PowerShell\MrToolkit\MrToolkit.p …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (System.Argument…rd errorRecord):ArgumentException) [Update-ModuleMan
ifest], ArgumentException
+ FullyQualifiedErrorId : UpdateManifestFileFail,Update-ModuleManifest

The sub-command used in the previous example creates a simple list of the function names:

Get-MrFunctionsToExport -Path .\GitHub\PowerShell\MrToolkit\ -Simple

functionstoexport5a.jpg

Luckily, I’ve written the Get-MrFunctionToExport function so it generates a list in the proper format that can be pasted into the module manifest if you happen to run into a scenario such as this one where Update-ModuleManifest can’t be used.

Get-MrFunctionsToExport -Path .\GitHub\PowerShell\MrToolkit\

functionstoexport3a.jpg

The easiest way to copy the results of the previous command is to simply pipe it to clip.exe:

Get-MrFunctionsToExport -Path .\GitHub\PowerShell\MrToolkit\ | clip.exe

functionstoexport4a.jpg

After pasting the results into the FunctionsToExport section of the module’s manifest (PSD1 file), the Pester test runs without issue:

Test-MrFunctionsToExport -ManifestPath .\GitHub\PowerShell\MrToolkit\MrToolkit.psd1

functionstoexport6a.jpg

Keeping track of functions and making sure they’re all specified in the FunctionsToExport section of the module manifest can be a annoying to maintain but it’s a necessary evil if you want module auto-loading to work properly when placing the functions from your modules in separate PS1 files.

Why not make your life a little easier by creating tools to make building other tools easier?

µ