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:

1Test-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:

 1function Get-MrFunctionsToExport {
 2
 3<#
 4.SYNOPSIS
 5    Returns a list of functions in the specified directory.
 6
 7.DESCRIPTION
 8    Get-MrFunctionsToExport is an advanced function which returns a list of functions
 9    that are each contained in single quotes and each separated by a comma unless the
10    simple parameter is specified in which case a simple list of the base file names
11    for the functions is returned.
12
13.PARAMETER Path
14    Path to the folder where the functions are located.
15
16.PARAMETER Exclude
17    Pattern to exclude. By default profile scripts and Pester tests are excluded.
18
19.PARAMETER Recurse
20    Return function names from subdirectories in addition to the specified directory.
21
22.PARAMETER Simple
23    Return a simple list instead of a quoted comma separated list.
24
25.EXAMPLE
26    Get-MrFunctionsToExport -Path .\MrToolkit
27
28.EXAMPLE
29    Get-MrFunctionsToExport -Path .\MrToolkit -Simple
30
31.INPUTS
32    None
33
34.OUTPUTS
35    String
36
37.NOTES
38    Author:  Mike F Robbins
39    Website: http://mikefrobbins.com
40    Twitter: @mikefrobbins
41#>
42
43    [CmdletBinding()]
44    param (
45        [ValidateScript({
46          If (Test-Path -Path $_ -PathType Container) {
47            $True
48          }
49          else {
50            Throw "'$_' is not a valid directory."
51          }
52        })]
53        [string]$Path = (Get-Location),
54
55        [string[]]$Exclude = ('*profile.ps1', '*.tests.ps1'),
56
57        [switch]$Recurse,
58
59        [switch]$Simple
60    )
61
62    $Params = @{
63        Exclude = $Exclude
64    }
65
66    if ($PSBoundParameters.Recurse) {
67        $Params.Recurse = $true
68    }
69
70    $results = Get-ChildItem -Path "$Path\*.ps1" @Params |
71               Select-Object -ExpandProperty BaseName
72
73    if ((-not($PSBoundParameters.Simple)) -and $results) {
74        $results = $results -join "', '"
75        Write-Output "'$results'"
76    }
77    elseif ($results) {
78        Write-Output $results
79    }
80
81}

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:

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

functionstoexport2a.jpg

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

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

1Get-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.

1Get-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:

1Get-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:

1Test-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?

µ