Scroll Top
Evotec Services sp. z o.o., ul. Drozdów 6, Mikołów, 43-190, Poland

PowerShell – Single PSM1 file versus multi-file modules

PSSHaredGoods

I've been working with PowerShell Modules for a while now, and most of my knowledge about them came from Warren F (psCookieMonster) blog about Building a PowerShell Module. It is a handy piece of information and recommended read for anyone starting up writing PowerShell Modules. He introduces an idea where a module is stored in multiple folders Private, Public, Bin, Lib and so on and having YourModule.psm1 as a wrapper for functions, binaries in those folders, where each function is stored in its file. It's an excellent concept, and I use it every day. It allows me to jump into a file quickly I want to find and fundamentally easier for development, especially in teams.

Description

Recently I've expanded on this approach, and when my modules started to get larger, I started creating more and more folders within folders to make sure things are a bit more structured. While it may not be for everyone, it suits my development style, and I'm quite happy with it. A few weeks ago Jeff Hicks mentioned that he is going to start converting his scripts from multi-file modules into single file modules when publishing to PowerShellGallery. While at that time I didn't give it much thought there was later on a conversation on twitter about benefits it may have for speed to load module. It seemed a bit odd that there would be any noticeable impact, so I left it alone for the next couple of weeks. Today while working with my PSSharedGoods module, which is my drop everything that looks like a function into it that currently has 123 PowerShell (.ps1) files and 20 folders, I've noticed this module is taking a bit too long to load slowing using some functions down. While it shouldn't be a big deal if a module is loaded and then reused multiple times this time I was using 1 of its functions and it was taking way too long. I've decided to measure it.

$ExecutionTime = [System.Diagnostics.Stopwatch]::StartNew()

Import-Module PSSharedGoods

$ExecutionTime.Stop()
$ExecutionTime.Elapsed

The test above isn't anything fancy. Just starting fresh PowerShell session and loading my module. I was a bit surprised with results.

It means it takes 12 to 15 seconds to Import-Module PSSharedGoods and uses a single function. And that's on I7 6700K with 32GB RAM, and SSD drive with 2500MB Write/Read speeds. Not too good. I decided to test a few things. First I've checked if there would be any change if I explicitly define FunctionsToExport in PSD1 and PSM1 file to find out it doesn't change much for speed. Than I decided to go Jeff's way and see how would converting 123 files into 1 would do. I've prepared a small function that would do it for me.

function Merge-Module {
    param (
        [string] $ModuleName,
        [string] $ModulePathSource,
        [string] $ModulePathTarget
    )
    $ScriptFunctions = @( Get-ChildItem -Path $ModulePathSource\*.ps1 -ErrorAction SilentlyContinue -Recurse )
    $ModulePSM = @( Get-ChildItem -Path $ModulePathSource\*.psm1 -ErrorAction SilentlyContinue -Recurse )

    foreach ($FilePath in $ScriptFunctions) {
        $Results = [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref]$null, [ref]$null)
        $Functions = $Results.EndBlock.Extent.Text
        $Functions | Add-Content -Path "$ModulePathTarget\$ModuleName.psm1"
    }

    foreach ($FilePath in $ModulePSM) {
        $Content = Get-Content $FilePath
        $Content | Add-Content -Path "$ModulePathTarget\$ModuleName.psm1"
    }
    Copy-Item -Path "$ModulePathSource\$ModuleName.psd1" "$ModulePathTarget\$ModuleName.psd1"
}

Then there's this one-liner from Mathias Jessen (@IISResetMe)

Get-ChildItem $ModulePath\*.ps1 |Get-Content | Add-Content $TargetDir\$ModuleName.psm1

Whichever method you will use doesn't matter as most likely it will need some tweaking depending on your module.

Speed comparison results

The result of this was a bit a shock to me. Exactly the same module converted from 123 files into 1 file from 12 seconds to less than 1 second.

I've yet to check how this affects my other modules, but it seems to be worthy of additional effort to convert your bigger modules into a single file for deployment purposes into PowerShellGallery. It may be even beneficial to build your modules more often for internal use since it seems to be faster than loading it in multiple files form.

Related Posts