LukeN
LukeN

Reputation: 1738

Invoking functions from nested modules in a script module do not always trigger a module to autoload

If I create a manifest module with nested modules, exported functions from all nested modules after the first do not appear in the list of available commands and don't trigger the module to autoload.

They also do not appear when I run "Get-Module -ListAvailable".

Only the exported functions from the first nested module appear in the list of commands.

If I explicitly import the module, all exported functions are available.

In the example below, Update-LegacyServices is not available until the module has been explicitly imported.

The only way I can make it work it to rename my module files to end with ps1 instead of psm1 and include them in ScriptsToProcess, which seems like a bad idea.

Module manifest (psd1)

@{

# Script module or binary module file associated with this manifest.
# RootModule = ''

# Version number of this module.
ModuleVersion = '1.0.0.1'

# ID used to uniquely identify this module
GUID = 'c11d6aca-d531-4d06-a732-5fb95113357f'

# Author of this module
Author = 'luke'

# Company or vendor of this module
CompanyName = ''

# Copyright statement for this module
Copyright = ''

# Description of the functionality provided by this module
# Description = 'MyBudget Developer Powershell Module'

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '4.0'

# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''

# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''

# Minimum version of the .NET Framework required by this module
DotNetFrameworkVersion = '4.5.0'

# Minimum version of the common language runtime (CLR) required by this module
CLRVersion = '4.0.30319.18444'

# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
RequiredModules = 'BitsTransfer'

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = @()

# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules =  @('database\Database.psm1', 'build\Build.psm1')

# Functions to export from this module
#FunctionsToExport = '*'

# Cmdlets to export from this module
CmdletsToExport = '*'

# Variables to export from this module
VariablesToExport = '*'

# Aliases to export from this module
AliasesToExport = '*'

# List of all modules packaged with this module.
ModuleList = @('database\Database.psm1', 'build\Build.psm1')

# List of all files packaged with this module
# FileList = @()

# Private data to pass to the module specified in RootModule/ModuleToProcess
# PrivateData = ''

# HelpInfo URI of this module
# HelpInfoURI = ''

# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''

}

Module 1 (Build\Build.psm1)

function Update-LegacyServices()
{
    echo "Update"
}

Export-ModuleMember -Function Update-LegacyServices

Module 2 (database\Database.psm1)

Function Get-Backup($directory, $name)
{
    echo "Get-Backup"
}

Export-ModuleMember -Function Get-Backup

Upvotes: 9

Views: 7204

Answers (2)

Blaise
Blaise

Reputation: 121

Hy Paul, Hy Luken,

After struggling for a few hours on this topic (with the implicit and declared manifest approach), I opted for the auto generated manifest. In essence, I wrote a ps1 script that generates the manifest, in two steps:

  1. with some 'business' logic, discover what you want to expose
  2. generate the module manifest

IMHO, Advantage of that approach: I 'fully' understand and master the exposure of the module content.

below you can find a PS code snippet that follows this approach, hoping it will benefit to other people.

# module discovery
$rootModule = "WdCore";
$modules = Get-ChildItem *.psm1;
$nestedmodulesNames = $modules | where { $_.BaseName -ne "WdCore"} | % { $_.BaseName } ;

# functions discovery
$modulesLines = $modules | Get-Content;
$functionsLines = $modulesLines | where { $_.contains("Function") };

$functionNamePattern = '^Function\s+(?<name>([a-z][0-9|A-Z|-]+))\s?{.*$';
$functionNames = $functionsLines | where { $_ -match $functionNamePattern } | select { $Matches['name'] } | % { $_.' $Matches[''name''] '};

# generate manifest
New-ModuleManifest `
    -Path ./WdTools.psd1 -RootModule $rootModule `
    -ModuleVersion '1.0' `
    -NestedModules $nestedmodulesNames `
    -FunctionsToExport $functionNames `
    -Guid '57D7F213-2316-4786-8D8A-3E4B9262B1E5' `
    -Author 'Blaise Braye' `
    -Description 'This module provides working directory tooling' `
    -PowerShellVersion '3.0' -ClrVersion '4.0';

Upvotes: 2

Paul Hicks
Paul Hicks

Reputation: 14019

I had this line in my .psd1 file

FunctionsToExport = 'FuncFromMainPsm1 FuncFromSecondPsm1'

which was what I inferred from the line generated by Powershell Tools for Visual Studio:

# Functions to export from this module
FunctionsToExport = '*'

This caused my Get-Module -ListAvailable to show what looked like the right thing

Script     1.0        MyMModule                  FuncFromMainPsm1 FuncFromSecondPsm1

But when I called FuncFromSecondPsm1 I'd get "The term 'FuncFromSecondPsm1' is not recognized...".

So I changed my export line to

FunctionsToExport = @('FuncFromMainPsm1', 'FuncFromSecondPsm1')

And now it all works. I can call both functions after my module loads, whether via autoload or Import-Module.

I have tried this with and without both ModuleList and FileList set. They make no difference.

Upvotes: 3

Related Questions