2

I have found that module auto-loading is unreliable. @briantist recommends explicitly importing required modules in finished code. This seems like good advice to me. The problem is I haven't found a reliable way to ensure that all required modules are explicitly imported.

Example

Supposed I have three modules with these dependencies:

m1.psm1 (depends on nothing)
m2.psm1 (calls functions in m1)

Suppose also that each of these modules has a set of automated tests that achieve 100% code coverage.

Running the tests for m2 only alerts you to a missing call to Import-Module m1 in the rare occurrence that PowerShell's auto-loading happens to fail to load m2 at the specific time you run the test. If you have several 10s of .psm1 files, the odds of missing a required Import-Module increases dramatically.

Suppose I mistakenly omit Import-Module m1 in m2.psm1 (after all, it passed all the automated tests) and put these modules into production. Each time functions in m2 are executed there is a small chance that m1 will not auto-load. When m1 does not successfully auto-load, the script fails to execute correctly that time, but may well execute correctly the time before and the time after. This will make it rather difficult to diagnose the cause of problem.

How do you ensure that all modules a script or module depends on have been explicitly imported?


Clarification: The modules I am developing are mostly automations that will ultimately run unattended on a server. Accordingly, I need to ensure that the functions in the modules will execute quickly and correctly once they are deployed on a machine other than my development workstation.

It is, of course, possible to import all modules you know you need explicitly when you start the powershell session. However, the auto-loading mechanism still masks the case where you forgot to explicitly load a module you depend on.

You can also blindly load all modules at startup (I do this at some stages during development), but depending on that means a start-up time on the order of 10s of seconds that grows with the size of your module library. That startup time will likely become a problem because it will be incurred each time a script is executed.

alx9r
  • 3,675
  • 4
  • 26
  • 55

3 Answers3

1

This is how i personally do it, for comparison, in my profile i have

# Get the contents of the module folder
$modules = Get-ChildItem "$PSScriptRoot\modules" -Directory

# For each item
ForEach($module in $modules)
{
    Try
    {
        # Try loading the module in the folder
        Import-Module $module.FullName
    }
    Catch
    {
        # Oh darn
        Write-Warning "Could not load $module"
    }
}

And in my module files i also have at the very top

#requires -version 3
#requires -module MyModule

Get-Module MyModule | Select -ExpandProperty RequiredModules | Import-Module
Bluecakes
  • 2,069
  • 17
  • 23
  • 1
    I pro-actively load all modules on startup as well. The problem is that that adds several seconds to starting up the powershell environment because it's blindly reading all modules. I've only just started my module library and it already takes several seconds to load them all. It's OK for development, but I worry that there will be cases in production where that amount of startup time to execute any script is a problem. – alx9r Mar 12 '15 at 22:48
  • Do you use every single module every single day though? I have 4 main modules that i use every single day and a host of others that i only use for specific situations, the above code is made dynamic so i can add a module to the "main pile" if i start to use it more frequently without having to alter profile code. I have a separate function and multiple aliases to load my other modules that i might use but don't load at startup. – Bluecakes Mar 12 '15 at 22:55
  • I think we might be concerned with different situations here. Most of the modules I write are automations that will ultimately run unattended on a server. – alx9r Mar 12 '15 at 23:10
1

I finally settled on the following strategy to detect any modules that have not been explicitly imported:

  • Use $PSModuleAutoLoadingPreference='none' to stop the powershell environment from auto-loading anything.
  • Never import any modules from a text fixture.
  • Remove as many modules as possible prior to running each test.

Pester Conventions

To achieve the above I settled on the following convention for Pester test fixtures:

Remove-egModules
$Global:mut = 'egMyModule'
Import-Module $mut

InModuleScope $mut {
    Describe{
        BeforeEach{ Remove-egModules -Except $mut }
        It 'does something useful' {
            ...
        }
    }
}

Remove-Variable 'mut' -Scope Global

Furthermore, any "helper" modules required by the test fixture but not by the module under test must be imported in the module under test despite that they are required only in the test fixture. Why? Suppose you import a helper module inside the test fixture and that helper module also happens to be used by the module under test. The tests will succeed even if you have not explicitly imported the helper module inside the module under test. When the test fixture imports a module, it is also made available to the module under test.

Remove-egModules

I determined that I needed to repeatedly remove all but the very minimum of modules when running tests. This is because modules seem to remain loaded for an entire powershell session. To achieve that, I use this function:

function Remove-egModules
{
    [CmdletBinding()]
    param
    (
        # this module does not get removed
        $Except 
    )
    process
    {
        Get-Module | 
            ? { $_.Name -ne 'egRemoveModule' } | # the module where this function is
            ? { 
                ...   # test for any other modules that should be removed
            } |
            ? { $_.Name -ne $Except } | 
            Remove-Module |
            Out-Null
    }
}
alx9r
  • 3,675
  • 4
  • 26
  • 55
0

Vote++ for Bluecakes' answer. My preference, though, is to control the list of modules using an array, that is, don't just load the modules found in the module path. Personal preference, I know. Also, I recommend dropping the modules prior to importing - otherwise PS will cache them for you in the current session, making development awkward (i.e.: you modify a module, but it doesn't get reloaded).

Simon Catlin
  • 2,141
  • 1
  • 13
  • 15