#PowerShell Script Module Design: Placing functions directly in the PSM1 file versus dot-sourcing separate PS1 files

So you’ve transitioned from writing PowerShell one-liners and scripts to creating reusable tools with functions and script modules. You may have started off by simply placing your functions in a PS1 file and dot-sourcing it. That leaves a lot to be desired though since it’s a manual process and even if you’ve added some code to your profile to accomplish that task, the experience still isn’t as good as it could be.

Many of the short comings can be alleviated by simply placing your functions into a PSM1 script module file, creating a PSD1 module manifest file, and placing those files into a folder with the same base name as those files underneath one of the modules folders located in your $env:PSModulePath.


The first path shown in the previous example is the current user module path. This will be different from the user who is currently logged into the computer if you’ve specified an alternate user when running an elevated PowerShell session. The second path is the all users path for modules which was added in PowerShell version 4. The third path (under Windows\System32) is a location where only Microsoft should place modules (per the PowerShell team).

The module manifest contains metadata about your script module and as a best practice you should always create a module manifest. If you plan to use PowerShellGet to upload modules to the PowerShell Gallery or to an internal private repository, there is certain metadata that is required.

The problem that I’ve encountered with this solution is when one large PSM1 script module file contains lots of functions it becomes difficult to maintain which has lead me to another solution. Placing your functions into PS1 files might not be such a bad idea after all but instead of manually dot-sourcing them, dot-source them from the PSM1 script module file. If each function is placed in its own PS1 file, that would make the functions a lot easier to maintain and it would also reduce the conflicts that are created when different functions in the same module are modified by different people and they’re checked into your source control system.

The PS1 files that contain the functions should be placed into the module folder with the PSM1 and PSD1 files to keep all of the files that are associated with the module grouped together. As I previously mentioned, dot-source them from the PSM1 file to make it work just as if the functions were placed directly in the PSM1 file.

Here’s the contents of a PSM1 file where I’m dot-sourcing all of the functions that are contained in the PS1 files which are located in the same folder as the script module itself:

One problem I’ve run into with this solution is that the functions don’t auto-load when attempting to use them and I certainly don’t want to go back to the dark ages where modules had to be manually imported.

The problem seems to be that using Export-ModuleMember -Function ‘*’ doesn’t work in the PSM1 file when dot-sourcing external functions. Specifying the individual function names themselves instead of an asterisk works without issue though. To be honest with you, there’s no reason to use Export-ModuleMember anyway since you should always have a module manifest and the functions to export can be controlled with it.

Specifying FunctionsToExport = ‘*’ in the module manifest doesn’t work either and even if it did, it would be slower than specifying the function names individually. So I’ve specified the functions names as shown in the following snippet of code from one of my module manifest files:

Specifying the individual function names seems to work without issue with the module auto-loading functionality that was introduced with PowerShell version 3.

Have you been down this path before? If so, I’d like your input on this subject. Feel free to leave a comment so others can learn from your experiences.



  1. Tim Pringle

    Nice article Mike.

    I keep every single function in a separate file, and do the dot sourcing of these in the PSM1 file like you’ve mentioned. Makes it easier for version control and (in some circumstances) re using the same function in another module.

    One other way i’ve thought about for module management is to actually build the module files within GitLab CI (which I use). That’d allow then functions to remain separate and version controlled and updateable relatively easily whilst still allowing single file integration.

    I’ve still to look into that properly though!

  2. Brett Osiewicz

    I, too, keep each function in a separate text file and every file is named according to the Function. I have another function (Get-FunctionFileName) that will parse every file name in the WindowsPowershell subdirectory looking for a matching pattern. If one is found, it brings it up in the script editor. Feel free to improve upon it, since I run into an occasional error.

    #requires -Version 2
    function Get-FunctionFileName {


    [Parameter(Mandatory = $true,
    ValueFromPipeline = $true,
    ValueFromPipelineByPropertyName = $true,
    ValueFromRemainingArguments = $false)]

    $Start = $PWD
    $ArrayofPaths = @( $env:PSModulePath -split ‘;’)
    $Collection = @()
    if ( $fn ) { Remove-Variable -Name fn -Scope Global -ea Continue }

    $Name = $Name -replace ‘\.\\’, ”
    $Name = $Name -replace ‘\.ps1’, ”

    foreach ($dir in $ArrayofPaths) {
    Write-Verbose -Message $dir
    $Collection += Get-ChildItem -Path $dir -Include “*$Name*” -Recurse | Where-Object -FilterScript { $_.PSIsContainer -eq $false }
    } #end foreach

    if (-not( $Collection)) {Write-Warning ‘No Results were found.’;break}

    [array]$Results = $Collection | Select-Object -Property BaseName, LastWriteTime, Length, Fullname,
    @{Name = ‘Module’; Expression = {Split-Path -Path $_.Directory -Leaf }}

    Write-Debug -Message ‘Check the `$Results variable’

    $Results | Format-Table -AutoSize -Property @{ Name = ‘Index’; Expression = { [array]::IndexOf($Results, $_) } },
    BaseName, LastWriteTime, Length, Fullname

    if ($Results.Count -gt 1) {$Choice = Read-Host -Prompt ‘Please Choose by Index Number(s)’} else {$Choice = 1}

    $Choice = $Choice -split { $_ -eq ‘ ‘ -or $_ -eq ‘,’ } |
    Where-Object -FilterScript { $_ } |
    ForEach-Object -Process { $_.trim() }

    $Choice | ForEach-Object -Process { [string[]]$global:fn += $Results[$_].Fullname }

    Write-Debug -Message ‘Would you like to check the status of $choice and $fn’

    Write-Output -InputObject $fn

    foreach ($File in $fn ) { psEdit -filenames $File }

  3. Ryan Yates (@ryanyates1990)

    Hi Mike,

    If you group functions in submodules and then list all of these submodules in the ModulesToProcess Array in the Module Manifest then it will AutoLoad correctly if placed in one of the PSModulePath locations (default or one that you have added yourself)

    Also I’ve never seen the need to explicitly use Export-ModuleMember though my Usecases have never included Private functions but if you do have Private Functions then using it in this manner should respect that.

    I used this in an older module that I’ve stopped maintaining but you can see the Module Manifest file and root module (spps.psm1) file and how I used to do it with in the Root Module call import-module for each submodule on Github at https://github.com/kilasuit/SPCSPS

    Hopefully the above reads ok.

  4. Kevin Marquette (@KevinMarquette)

    I am in the middle of rethinking how I do this because of some of the issues that you mentioned. I have all my functions in a ‘functions’ sub folder as individual files that I dot source when my module loads like you have. I add a little bit to exclude my pester tests and add verbosity. The individual file approach is a good pattern that works well with Pester.

    “$moduleRoot\Functions\*.ps1” |
    Resolve-Path |
    Where-Object { -not ($_.ProviderPath.Contains(“.Tests.”)) } |
    ForEach-Object { . $_.ProviderPath ; Write-Verbose $_.ProviderPath}

    I don’t export module members with the Export-ModuleMember or add them to my module manifest. This is where I have my issue with auto loading and such. I am considering just adding the Export-ModuleMember at the end of every function file.

    My thinking is that I have some functions that are more helper functions and really should not be exported. the counter to this is that because my helper functions are exported, I put a lot of effort into the quality of them.

  5. itpraktyk


    Thank you for your post.

    Some of us still need use PowerShell 2.0 (when work with Exchange Server 2010 on Windows Server 2008 R2).

    For my module https://github.com/it-praktyk/Invoke-MailboxDatabaseRepair in my psm1 I’ve used workaround like below.

    If ($PSVersionTable.psversion.major -lt 3) {

    $RequiredFiles = Get-ChildItem -Path “$PSScriptRoot\Nested\” -Filter “*.ps1”

    $RequiredFiles | Foreach-Object -Process { Import-Module $_.FullName -ErrorAction Stop -verbose:$false }


    Is any better way to accomplish this?

  6. Adrian Rodriguez

    I’ve stopped using a wildcard as the value of FunctionsToExport and now explicitly list all public functions contained within my module. I do this primarily so the functions contained within my module are easily viewable from a Nuget repository.

  7. ngetchell

    I always liked how Warren Frame handles the loading of functions since I write a ton of private functions. An example of his PSM1 is here https://github.com/RamblingCookieMonster/PSStackExchange/blob/master/PSStackExchange/PSStackExchange.psm1. Notice that he uses Export-ModuleMember but dynamically loads each function in the public folder. Add a new ps1 in the public directory and now there is no need to touch the PSM1.

  8. John Thayer Jensen

    Once I created a manifest for my module, I can’t get the thing to export the functions no matter what I do! I’m lost!!


  9. John Thayer Jensen

    Got it to work. I didn’t realise the manifest had to have the name of the module in the root module value.


    • Mike Robertson

      Thank you! Thank you! Thank you!
      You were the first person I’ve found that both experienced this problem and found the right solution. Exactly what I needed!

  10. Amanda Debler

    I’m currently trying to unwind a giant dot-sourced pile of functions without parameters (relying on global variables) that I inherited into parameterized functions and a proper module. Any suggestions for tracking down the references between files?


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: