Collection of Cmdlets/Functions, Best Practice

This topic contains 10 replies, has 5 voices, and was last updated by Profile photo of Dave Wyatt Dave Wyatt 1 year, 9 months ago.

  • Author
    Posts
  • #19352
    Profile photo of Anthony Stewart
    Anthony Stewart
    Participant

    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?

  • #19353
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    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 [b]FunctionsToExport = '*'[/b] to your manifest.

  • #19354
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Why not put both functions in ththe same module?

  • #19355
    Profile photo of Anthony Stewart
    Anthony Stewart
    Participant

    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 [b]Test-ModuleManifest Misc.psd1[/b] exports both commands.
    But if I comment out RootModule and [b]Test-ModuleManifest Misc.psd1[/b], 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.

  • #19356
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    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?

  • #19357
    Profile photo of Don Jones
    Don Jones
    Keymaster

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

  • #19358
    Profile photo of Don Jones
    Don Jones
    Keymaster

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

  • #19362
    Profile photo of Anthony Stewart
    Anthony Stewart
    Participant

    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
            }
        }
    }
    
  • #22644

    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

  • #22647
    Profile photo of Tim Pringle
    Tim Pringle
    Participant

    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%28v=vs.85%29.aspx[/url]

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

  • #22650
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    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.)

You must be logged in to reply to this topic.