PowerShell

PowerShell – Single PSM1 file versus multi-file modules

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.

This post was last modified on December 3, 2018 20:05

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

4 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

6 months ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

7 months ago

How to Efficiently Remove Comments from Your PowerShell Script

As part of my daily development, I create lots of code that I subsequently comment…

7 months ago

Unlocking PowerShell Magic: Different Approach to Creating ‘Empty’ PSCustomObjects

Today I saw an article from Christian Ritter, "PowerShell: Creating an "empty" PSCustomObject" on X…

8 months ago

Report Active Directory Accounts that are Synchronized with Azure AD

I was scrolling X (aka Twitter) today and saw this blog post, "PowerShell: Report On-Premises…

8 months ago