Powershell: Exported alias autoloading from PSM1 module not working

Created on 16 Apr 2019  路  11Comments  路  Source: PowerShell/PowerShell

My understanding (and experience of PowerShell v5) is that Modules (.psm1 files) in the Modules\ folder are automatically loaded (unless $PSModuleAutoLoadingPreference is set to none). This appears to be broken in PWSH - at least the alias is not imported/available.

Steps to reproduce

powowshell.psm1

function Invoke-PowowShell {
    [CmdletBinding(SupportsShouldProcess)]
    [Alias('pow')]
    #...code here...
    "You have the POWer!"
}
New-Alias -Name pow -Value Invoke-PowowShell
Export-ModuleMember -Function Invoke-PowowShell -Alias pow

Copy the above file to Modules\PowowShell and restart PowerShell Core (PWSH).

Expected behavior

I expect Invoke-PowowShell and the alias pow to be available to me in all PowerShell sessions:

PS> pow
You have the POWer!

Actual behavior

PS> pow
pow : The term 'pow' is not recognized as the name of a cmdlet, function, script file, or operable program.
...

PS> Invoke-PowowShell
You have the POWer!

So the function Invoke-PowowShell is installed but not the alias pow.

More detail:

PS > Get-Command pow
Get-Command : The term 'pow' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ Get-Command pow
+ ~~~~~~~~~~~~~~~
+ CategoryInfo          : ObjectNotFound: (pow:String) [Get-Command], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException,Microsoft.PowerShell.Commands.GetCommandCommand

PS> Get-Command Invoke-PowowShell

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Invoke-PowowShell                                  0.0        PowowShell

Get-Module:

PS> Get-Module -ListAvailable
    Directory: C:\Users\me\Documents\PowerShell\Modules
ModuleType Version    Name                                PSEdition ExportedCommands
---------- -------    ----                                --------- ----------------
Script     0.0        Hello                               Desk      Invoke-Hello
Script     0.0        PowowShell                          Desk      Invoke-PowowShell

Environment data

Name                           Value
----                           -----
PSVersion                      6.2.0
PSEdition                      Core
GitCommitId                    6.2.0
OS                             Microsoft Windows 10.0.16299
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Area-Cmdlets-Core Issue-Bug Resolution-By Design

All 11 comments

_Do_ modules autoload when you don't have a PSD1 manifest file? 馃

It's been so long since I wrote a module without a PSD1 that it hasn't occurred to me in forever. I wouldn't _expect_ such a module to autoload, because there's no way to pull the metadata for the module without fully importing it.

They definitely do autoload even without a manifest

I tried this out and it's working for me on Windows 10 with PowerShell 6.2.

However, there's a problem with the function: the attributes fail parsing because there's no param block.

If you call ipmo PowowShell I think you'll see an error message describing this.

When I changed psm1 contents to this:

function Invoke-PowowShell {
    [CmdletBinding(SupportsShouldProcess)]
    [Alias('pow')]

    param() # <---- PARAM BLOCK

    #...code here...
    "You have the POWer!"
}
New-Alias -Name pow -Value Invoke-PowowShell
Export-ModuleMember -Function Invoke-PowowShell -Alias pow

Everything worked as expected.

For a full working example:

$modDir = ($env:PSModulePath -split ';')[0]

$modPath = Join-Path $modDir 'powowshell'

mkdir $modPath

$psm1Path = Join-Path $modPath 'powowshell.psm1'

New-Item -Path $psm1Path -Value @'
function Invoke-PowowShell {
    [CmdletBinding(SupportsShouldProcess)]
    [Alias('pow')]
    param()
    #...code here...
    "You have the POWer!"
}
New-Alias -Name pow -Value Invoke-PowowShell
Export-ModuleMember -Function Invoke-PowowShell -Alias pow
'@

pow

Update: The above is a repro of the bug

Summary:

  • Invoke-PowowShell works
  • pow does not
  • Upon loading of the module, pow works as expected

Apologies, trying this in a new session with only pow did not work and appears to work in Windows PowerShell.

I just checked with PowerShell 6.1.1 and 6.0.5, it looks like this may never have worked in PowerShell 6.

@rjmholt It is not bug. It is "by design". It seems we never analyze module's aliases for performance reasons.
Module manifests work for this purpose.

OK, hard luck for guys like me who are writing their first module. Maybe a warning should be issued that this ain't gonna work properly without a manifest?

It does seem kind of odd to allow the -Alias parameter on the Export-ModuleMember cmdlet if it can't be used. I'm more inclined to think it _is_ actually a bug, especially since it did previously work on Windows PowerShell.

The fundamental problem is that we have to parse a command in the module to work out what it exports. This is not practicable in general:

$body = (Invoke-WebRequest -Uri 'https://mywebsite.com/api/pwsh-export').Body

Export-ModuleMember -Function ($body.Functions)

There's no way to determine what that actually exports without running the code and sending a network request.

It's a contrived example perhaps, but it's (1) quite possible and (2) representative of things like string templating, variable mangling and cmdlet use (Join-Path for example) that might be used.

So we can never actually always determine the exports, it's just not possible. I personally dislike that we try, since it means exports like this are only reliable if you provide concrete values to Export-ModuleMember -- so when you get more clever and it stops working, it's hard to explain why.

@iSazonov I understand the principle, although I'm not entirely sure it's intentional. There's code that seems to do it here:

https://github.com/PowerShell/PowerShell/blob/50efc4192d99bbbe20c34e03ab04aa2f7d7d82b1/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs#L20-L91

However, I haven't been able to catch it in the debugger at all.

I've been looking into what it would take to make this work, but the fundamental advice is: Write a module manifest. It's much much better.

I understand the principle, although I'm not entirely sure it's intentional.

I can not find the comment but it was and it seems the comment was from Jason.

Taking into account that currently community has a large number of modules, the rule is to use a manifest. So I think we can close this issue with "By design" until we get important business case.

Was this page helpful?
0 / 5 - 0 ratings