Search code examples
powershell

PowerShell is failling to auto import module, if the command uses a 'unapproved verb'


if I have a module called foo

C:\Users\gary\Documents\PowerShell\Modules\foo\foo.psm1
C:\Users\gary\Documents\PowerShell\Modules\foo\foo.psd1

With the content of foo.psd1 being:

@{
    ModuleVersion = '0.0.1'
    FunctionsToExport = @('*')
    RootModule           = 'foo.psm1'
}

and foo.psm1:

Function Encode-Path{
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, Mandatory)]
        $Path
    )
    Process {"Some process"}
    End {"Ending..."}
}
Function Decode-Path{
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, Mandatory)]
        $Path
    )
    Process {"Some process"}
    End {"Ending..."}
}

Simply calling the Encode-Path at the shell will fail with:

Encode-Path: The term 'Encode-Path' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

I sometimes fix this by calling pwsh.exe within the session and then:

Get-Module -ListAvailable 

but it too sometimes does not even work and when it does there is a long delay, as it will import every module on my system.

I know this issue is being caused by my use of unapproved verb. I really don't like the <verb><noun> at all. I don't work with a team, I just use PowerShell to make my life easy, so please don't just suggest I get on board with it.

Searching around I have not found any solution for this, I just keep coming across solutions for Import-Module -DisableNameChecking which is addresses a separate issue, which is to supress warning messages about "unapproved verbs"

I am just looking for a way to auto import modules as I invoke their commands, Ideally avoid having to import all modules at once.


Solution

  • Your symptom isn't consistent with your problem description / sample code.
    PowerShell's de facto behavior is as follows (written as of v7.4.x):

    • Auto-loading happens for any module placed in one of the directories listed in $env:PSModulePath.

    • When auto-loading happens, there is no warning for exported commands that do not adhere to PowerShell's naming convention, i.e. commands that do not use the <verb>-<noun> convention, with <verb> needing to be one of the approved verbs.

    • By contrast, explicitly loading (importing) a given module that exports commands with nonstandard names does generate a warning:

      • If Import-Module is used for explicit importing, you can use the -DisableNameChecking switch to silence the warning.

      • If using module is used - which is a must if you want to import class and enum definitions from a script module - you can NOT silence the warning.

    • If you want to ensure that no warning is ever emitted:

      • Give your functions a standard name to begin with - which you must export too, however.

      • Define - and export - aliases for those functions, using the desired nonstandard names (alias names are not subject to the warning) - see the bottom section for an example.


    In other words: if your module is located in one of the directories in $env:PSModulePath, loading it implicitly by invoking one of its exported commands will not result in a warning.


    Get-Module -ListAvailable [...] when it does there is a long delay, as it will import every module on my system.

    • -ListAvailable does not import every module - it simply reports all modules that are discoverable via $env:PSModulePath, along with their exported commands an aliases.

    • However, you can help speed up discovery by avoiding wildcard expressions; e.g., instead of using FunctionsToExport = @('*') in your module manifest, enumerate the functions to export explicitly: `FunctionsToExport = @('Encode-Path', 'Decode-Path')


    Self-contained sample code that demonstrates combining arbitrarily named aliases with functions with standard names:
    # Create a directory for a `sample` module in the current user's module location:
    
    # Get the root dir. of the current user's modules.
    $currUserModuleRootDir = ("$(Split-Path $PROFILE)\Modules", "$HOME/.local/share/powershell/Modules")[$env:OS -ne 'Windows_NT']
    
    # Create a 'sample' subdirectory for it, to create a module by that name.
    $sampleModuleDir = New-Item -Force -Type Directory (Join-Path $currUserModuleRootDir sample)
    
    # Create the root script module (`*.psm1`):
    @'
      # Define the functions with *standard* names.
      Function ConvertTo-__Path {
        [CmdletBinding()]
        Param(
            [Parameter(ValueFromPipeline, Mandatory)]
            $Path
        )
        Process {"Some process"}
        End {"Ending..."}
      }
    
      Function ConvertFrom-__Path {
        [CmdletBinding()]
        Param(
            [Parameter(ValueFromPipeline, Mandatory)]
            $Path
        )
        Process {"Some process"}
        End {"Ending..."}
      }
    
      # Now define *alias* with the desired - nonstandard - names:
      Set-Alias Encode-Path ConvertTo-__Path
      Set-Alias Decode-Path ConvertFrom-__Path
    
    '@ > (Join-Path $sampleModuleDir sample.psm1)
             
    # Create the module's manifest:
    @'
      @{
        ModuleVersion = '0.0.1'
        FunctionsToExport = @('ConvertTo-__Path', 'ConvertFrom-__Path')
        AliasesExport = @('Encode-Path', 'Decode-Path')
        RootModule           = 'sample.psm1'
      }
    '@ > (Join-Path $sampleModuleDir sample.psd1)
    

    After running the above:

    • Aliases Encode-Path and Decode-Path should be auto-discovered
    • Explicitly running Import-Module sample or using module sample should work without warning.

    If you later want to remove the sample module, run the following (you may have to run Remove-Module sample first):

    Remove-Item -Recurse (Join-Path ("$(Split-Path $PROFILE)\Modules", "$HOME/.local/share/powershell/Modules")[$env:OS -ne 'Windows_NT'] sample)