Collection of Cmdlets/Functions, Best Practice

There have been a couple times where I have needed to make a single cmdlet(function) that does a relatively simple task, such as remove an item on a remote computer, and get the computer number based on it’s name.
I wrote both of these functions and tested them, and they seem to work correctly.
So, what I want to do is to put these two (or more) functions into their own .psm1 files, so that if I want to, I can import them individually, but I also want to be able to import all of them (if I ever need to).

Here is my folder structure:

|-Modules
  |-Misc
    |-Misc.psd1
    |-Misc.psm1
    |-GetComputerNumber.psm1
    |-RemoveRemoteItem.psm1

The only way, that I have found based on Microsoft’s How to Write a Module Manifest (url)http://msdn.microsoft.com/en-us/library/dd878297(v=vs.85).aspx(/url), is to have at least the following.

Misc.psd1

@{
RootModule = 'Misc.psm1'
NestedModules = @("GetComputerNumber.psm1", "RemoveRemoteItem.psm1")
}

Misc.psm1

Export-ModuleMember Get-ComputerNumber
Export-ModuleMember Remove-RemoteItem

If I don’t have the Misc.psm1 file, then powershell only imports the first function(s) in the first listed item in NestedModules.

So, is there a way where I don’t have to Misc.psm1 in which I explicitly export each function?

Module manifests have properties FunctionsToExport, VariablesToExport, CmdletsToExport and AliasesToExport which correspond to what you are doing with the Export-ModuleMember cmdlet in Misc.psm1. By default, all functions from the nested modules should be exporting just fine, but just in case, you can add FunctionsToExport = ‘*’ to your manifest.

Why not put both functions in ththe same module?

Here is my actual Module Manifest:

Misc.psd1 (commented lines removed)

@{
RootModule = 'Misc.psm1'
ModuleVersion = '0.0'
GUID = 'fa5f6060-9c8a-4527-9956-d8e37cea41bf'
Author = 'Anthony Stewart'
CompanyName = 'OIT@UTK'
Copyright = '(c) 2014 Anthony Stewart. All rights reserved.'
Description = 'This module is a collection of funtions and cmdlets written by OIT, for OIT.'
NestedModules = @("GetComputerNumber.psm1", "RemoveRemoteItem.psm1")
FunctionsToExport = '*'
CmdletsToExport = '*'
VariablesToExport = '*'
AliasesToExport = '*'
}

Running Test-ModuleManifest Misc.psd1 exports both commands.
But if I comment out RootModule and Test-ModuleManifest Misc.psd1, then it exports just the first command.

According to Microsoft, your method should work, I just haven’t gotten it to work yet.
[blockquote]The members of the nested modules are imported into the caller’s session state only when the root module exports the nested module members explicitly or when the root module omits the Export-ModuleMember command.[/blockquote]

On a side note, when I click on the any of the toolbar buttons (in Chrome), it inserts the tags with parenthesis surrounding them instead of brackets. It works fine in IE. Not sure if the Mods are aware of this.

I haven’t been able to reproduce this behavior on my computer yet. Is there anything in these nested psm1 files other than the function definitions? (Calls to Export-ModuleMember, etc?) What version of PowerShell are you using?

I meant, why not put both functions in the same PSM1 file?

I meant, why not put both functions in the same PSM1 file?

Sorry Don, I didn’t see your reply.
I can do that, I just think that having the modules separate would make collaboration and maintainability easier.
Of course I could be way off base, especially since there are only a couple people developing powershell scripts in my group.

I’m developing on a Windows 8.1 machine with PS 4.0, but my code will mainly be run on Windows 7 with PS 3.0.

Here are the actual files, sorry for the wall of text, I can edit and attach as txt files if that’s what you want.

If you see a bug, or an easier way to do something, you can tell me, but that’s not the main reason of this post.

Misc.psd1

#
# Module manifest for module 'Misc'
#
# Generated by: astewa44
#
# Generated on: 10/3/2014
#

@{

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

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

# ID used to uniquely identify this module
GUID = 'fa5f6060-9c8a-4527-9956-d8e37cea41bf'

# Author of this module
Author = 'Anthony Stewart'

# Company or vendor of this module
CompanyName = 'OIT@UTK'

# Copyright statement for this module
Copyright = '(c) 2014 Anthony Stewart. All rights reserved.'

# Description of the functionality provided by this module
Description = 'This module is a collection of funtions and cmdlets written by OIT, for OIT.'

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

# 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 Microsoft .NET Framework required by this module
# DotNetFrameworkVersion = ''

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

# 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 = @()

# 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 = @("GetComputerNumber.psm1", "RemoveRemoteItem.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 = @()

# 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 = ''

}

Misc.psm1

Export-ModuleMember Get-ComputerNumber
Export-ModuleMember Remove-RemoteItem

GetComputerNumber.psm1

 Get-ComputerNumber -Name "COM-NORTHD99"
   99
#>
function Get-ComputerNumber
{
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [string]
        $Name
    )

    $CompNum = $Name
    $CN = -1
    for ($i = 0; ($i -lt $Name.Length) -and (-not [int32]::TryParse($CompNum, [ref]$CN)); $i++) 
    { 
        Write-Verbose $CompNum
        $CompNum = $CompNum.Substring(1)
    }

    if ([string]::IsNullOrEmpty($CompNum))
    {
        Write-Verbose "Number not found"
        return -1
    }
    else
    {
        Write-Verbose "Number found"
        return $CN
    }
}

RemoveRemoteItem.psm1

 Remove-RemoteItem -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\R-core -ComputerNames "COM-NORTHD99" -Recurse
#>
function Remove-RemoteItem
{
    [CmdletBinding()]
    Param
    (
        # Path - The path to the item to be removed
        [Parameter(Mandatory=$true,
                   Position=0)]
        [string[]]
        $Path,

        # ComputerName - The computer names for the Remove-Item cmdlet to be run on
        [Parameter(Mandatory=$true,
                   Position=1)]
        [string[]]
        $ComputerNames,

        # Recurse - Passed directly to Remove-Item
        [switch]
        $Recurse,

        # Force - Passed directly to Remove-Item
        [switch]
        $Force
    )

    # Hashtable for splatting the parameters to Remove-Item
    $params = @{Path=$Path; Recurse=$Recurse; Force=$Force}

    foreach ($computer in $ComputerNames)
    {
        # If the computer name is not the local computer
        if ($computer -ne $env:COMPUTERNAME)
        {
            Invoke-Command -ComputerName $computer {Remove-Item @params}
        }
        else
        {
            Remove-Item @params
        }
    }
}

Hello everyone,

Have you found a solution to this?

I have a manifest very much like Anthony’s but my nested module is binary.

The functions in the binary module show up when using the Test-ModuleManifest cmdlet but are not available after importing the module.

I also confirmed that the nested module gets imported by creating a function in the root module (script module) calling a function from the nested module.

Moreover, if the nested module is script, it works fine.

Regards,

Chris

Hey Chris,

It may not be related, but Microsoft do indicate that there are certain restrictions when you import a snap-in assembly as a module. The article is at [url]https://msdn.microsoft.com/en-us/library/dd878342(v=vs.85).aspx[/url]

Also, have you tried registering the snap-in in your main .psm1 file, via Add-PSSnapin instead via the nested module route?

I still can’t reproduce the problem. I’ve copied the files the OP posted, and both commands are exported regardless of whether I use the RootModule or not. (I do have the PS 5.0 preview installed, though. Will try it out later on 4.0 and 3.0 systems, as time allows.)