Updated 2018-02-25: Maybe don't do this if you have special configuration data in the PSData.PrivateData section. Update-ModuleManifest (and New-ModuleManifest) remove many properties from the PrivateData hash table and puts them in the PSData hash table instead. Awful!

If you've written a PowerShell module you'll be familiar with the FunctionsToExport portion of the .psd1 module manifest that starts out like this:

This array defines what functions should be accessible from outside of the module, being almost everything if not everything. You can put @("*") here but sometimes this will result in auto-complete of function names not working until you've imported the module at least once, and then one day auto-complete may just stop working entirely due to caching bugs.

This demonstrates the problem using a fresh module named AutoFunction which holds a dummy function named Start-AutoFunction:

The "best practice" and proper way of handling this then is to add an entry to the array in this file every time you create a new function. Manually. What a pain in the ass right? But there's a better way!

If you haven't seen psake before you can take a look later. Start up PowerShell as Administrator and install psake from the PowerShell Gallery using Install-Module psake and then you're ready to go.

How does psake work? It's the PowerShell equivalent of a Makefile. Basically:

  • You add a file in the root of your module called psakefile.ps1
  • When you run Invoke-psake from that location, it will load and execute the file

Meanwhile the syntax of the file is extremely straightforward:

Task default -depends FunctionsToExport

Task FunctionsToExport {
    # ...
}

When you run Invoke-psake it runs the default Task. In this case the default Task depends on another Task. And inside that Task's script block is the normal PowerShell code you use every day.

So now we have a simple way of triggering maintenance to our module, how would we go about setting the FunctionsToExport line? It would go like this:

  • Work out the name of the module from the current folder name
  • Parse through Verb-Noun.ps1 files looking for function names
  • Write them to the module manifest
  • Reload the module
Task default -depends FunctionsToExport

Task FunctionsToExport {
    $moduleName = Get-Item . | ForEach-Object BaseName

    # RegEx matches files like Verb-Noun.ps1 only, not psakefile.ps1 or *-*.Tests.ps1
    $functionNames = Get-ChildItem -Recurse | Where-Object { $_.Name -match "^[^\.]+-[^\.]+\.ps1$" } -PipelineVariable file | ForEach-Object {
        $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref] $null, [ref] $null)
        if ($ast.EndBlock.Statements.Name) {
            $ast.EndBlock.Statements.Name
        }
    }
    Write-Verbose "Using functions $functionNames"

    Update-ModuleManifest -Path ".\$($moduleName).psd1" -FunctionsToExport $functionNames
    Import-Module $moduleName -Force -Verbose:$false
}

So let's try it out!

It works!

Now that you know how to do this, what other useful stuff could you pack into your psakefile? Perhaps checking you haven't duplicated a file/function more than once?

Source code for the demo module is available here if you need it.