Get-Command in PowerShell 3 (NOTE: CTP2 Bug causes module loading)

I don’t normally blog about the bugs I find in beta software, but I posted this bug to PowerShell’s Connect and I feel like it got ignored and not voted, so I’m going to try to explain myself better here … The bug is on Connect, but let me talk to you first about how Get-Command is supposed to work.

In PowerShell, Get-Command is a command that serves two purposes: first it lets you search for commands using verb, noun, wildcards, module names etc. and then it also returns metadata about commands. In PowerShell 2, it could only search commands that were in modules (or snapins) you had already imported, or executables & scripts that were in your PATH.

So here’s the deal: Get-Command has always behaved differently when it thinks you’re searching. The only way it can tell that you’re searching is that you don’t provide a full command name. So, if you use a wildcard (e.g.: Get-Command Get-Acl* or even Get-Command Get-Ac[l]), or search using a Noun or Verb (e.g.: Get-Command -Verb Get or Get-Command -Noun Acl or even Get-Command -Verb Get -Noun Acl), then PowerShell assumes you’re searching (and won’t throw an error when no command is found).

In PowerShell 3, because modules can be loaded automatically when you try to run a command from them, Get-Command had to be modified to be able to return commands that aren’t already loaded. The problem the PowerShell team faced is that in order to get the metadata about a command, they needed to actually import the module. What they came up with is that if you’re searching … then Get-Command will not load modules which aren’t already loaded. If you specify a full command name with no wildcards, then PowerShell will load any module(s) where it finds a matching command in order to get the metadata (parameter sets, assembly info, help, etc). And of course, if you specify a full command that doesn’t exist, you’ll get an error!

Perhaps a few examples will help:

Launch PowerShell 3 using:

powershell -noprofile -noexit -command "function prompt {'[$($myinvocation.historyID)]: '}"
 

And then try this, noticing how much more information you get when you specify a specific full name:


[1]: Get-Module
[2]: Import-Module Microsoft.PowerShell.Utility
[3]: Get-Command -Verb Get -Noun Acl | Format-List

Name             : Get-Acl
Capability       : Cmdlet
Definition       : Get-Acl
Path             :
AssemblyInfo     :
DLL              :
HelpFile         :
ParameterSets    : {}
ImplementingType :
Verb             : Get
Noun             : Acl


[4]: Get-Module

ModuleType Name                                ExportedCommands
---------- ----                                ----------------
Manifest   Microsoft.PowerShell.Utility        {Add-Member, ...}

[5]: Get-Command Get-Acl | Format-List

Name             : Get-Acl
Capability       : Cmdlet
Definition       : Get-Acl [[-Path] <string[]>] [-Audit]
                   [-AllCentralAccessPolicies] [-Filter <string>]
                   [-Include <string[]>] [-Exclude <string[]>]
                   [-UseTransaction] [<CommonParameters>]

                   Get-Acl -InputObject <psobject> [-Audit]
                   [-AllCentralAccessPolicies] [-Filter <string>]
                   [-Include <string[]>] [-Exclude <string[]>]
                   [-UseTransaction] [<CommonParameters>]

                   Get-Acl [[-LiteralPath] <string[]>] [-Audit]
                   [-AllCentralAccessPolicies] [-Filter <string>]
                   [-Include <string[]>] [-Exclude <string[]>]
                   [-UseTransaction] [<CommonParameters>]
Path             :
AssemblyInfo     :
DLL              : C:\Windows\Microsoft.Net\assembly\GAC_MSIL\
                   Microsoft.PowerShell.Security\
                   v4.0_3.0.0.0__31bf3856ad364e35\
                   Microsoft.PowerShell.Security.dll
HelpFile         : Microsoft.PowerShell.Security.dll-Help.xml
ParameterSets    : {[[-Path] <string[]>] [-Audit]
                   [-AllCentralAccessPolicies] [-Filter <string>]
                   [-Include <string[]>] [-Exclude <string[]>]
                   [-UseTransaction] [<CommonParameters>],
                   -InputObject <psobject> [-Audit]
                   [-AllCentralAccessPolicies] [-Filter <string>]
                   [-Include <string[]>] [-Exclude <string[]>]
                   [-UseTransaction] [<CommonParameters>],
                   [[-LiteralPath] <string[]>] [-Audit]
                   [-AllCentralAccessPolicies] [-Filter <string>]
                   [-Include <string[]>] [-Exclude <string[]>]
                   [-UseTransaction] [<CommonParameters>]}
ImplementingType : Microsoft.PowerShell.Commands.GetAclCommand
Verb             : Get
Noun             : Acl


[6]: Get-Module

ModuleType Name                                ExportedCommands
---------- ----                                ----------------
Manifest   Microsoft.PowerShell.Security       {ConvertFrom-Sec...}
Manifest   Microsoft.PowerShell.Utility        {Add-Member, ...}
 

But there are several problems:

Get-Command has another parameter: -Module, which allows you to specify which modules should be searched, and in PowerShell 3, it changes the behavior in weird (buggy) ways:

  1. If you specify a single module, then that module is imported (to search it more thoroughly?), even if you specify a specific command that’s not in that module.
  2. If you specify a single module that does not have a command that matches, then Microsoft.PowerShell.Management is loaded also. I don’t know why yet.
  3. If you specify more than one module, and you’re searching, and none of them have a command that matches … it’s just as though you hadn’t specified modules, and nothing unexpected happens.
  4. If you specify more than one module, and a specific command, then it gets really wierd:
    • If the command is in one (or more) of the specified modules, the first module (in PATH order, not the order you specified) which you listed that has the command is imported.
    • If it’s a valid command in a different module, the first module with the command is loaded … and so is Microsoft.PowerShell.Management. I don’t know why! Oh, and you still get the error because it can’t find the command where you told it to look.

I filed a bug on Connect to cover that last scenario where the module containing the command is loaded even though you gave Get-Command a list of modules to look in, here’s another example, and notice that even though all I do here is run the same command over and over (I added some Get-Module to show you WHY you get these results, but it’s the same without them), but I get different results:


[1]: Import-Module Microsoft.PowerShell.Utility
[2]: Get-Module

ModuleType Name                                ExportedCommands
---------- ----                                ----------------
Manifest   Microsoft.PowerShell.Utility        {Add-Member, ...}


[3]: Get-Command Get-Acl -module (Get-Module) # Passes one module
Get-Command : The term 'get-acl' 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.

[4]: Get-Module

ModuleType Name                                ExportedCommands
---------- ----                                ----------------
Manifest   Microsoft.PowerShell.Management     {Add-Computer, ...}
Manifest   Microsoft.PowerShell.Utility        {Add-Member, ...}


[5]: Get-Command Get-Acl -module (Get-Module) # Passes two modules
Get-Command : The term 'get-acl' 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.

[6]: Get-Module

ModuleType Name                                ExportedCommands
---------- ----                                ----------------
Manifest   Microsoft.PowerShell.Management     {Add-Computer, ...}
Manifest   Microsoft.PowerShell.Security       {ConvertFrom-Sec...}
Manifest   Microsoft.PowerShell.Utility        {Add-Member, ...}

[7]: # This time it will include Microsoft.PowerShell.Security!
[7]: Get-Command Get-Acl -module (Get-Module)

Capability      Name                ModuleName
----------      ----                ----------
Cmdlet          Get-Acl             Microsoft.PowerShell.Security
 

Similar Posts: