PowerShell: Wrong module is auto-loaded when using unqualified cmdlet and multiple modules contain a cmdlet by that name

Prerequisites

Steps to reproduce

  1. Install the VMWare PowerCLI module. Available in the PowerShell gallery at: https://www.powershellgallery.com/packages/VMware.PowerCLI
  2. Open a new PowerShell instance with standard settings (including, but not limited to, PSModuleAutoloadingPreference being undefined). [ Tip: Use -NoProfile if your profile load any module or modifies any settings. ]
  3. Run Get-VM.

Expected behavior

> Get-VM

Name                             State CPUUsage(%) MemoryAssigned(M) Uptime   Status             Version
----                             ----- ----------- ----------------- ------   ------             -------
Ubuntu                           Off   0           0                 00:00:00 Operating normally 11.0
Ubuntu 22.04 LTS                 Off   0           0                 00:00:00 Operating normally 11.0
Windows 10 Enterprise 21H1       Off   0           0                 00:00:00 Operating normally 10.0
.......

Actual behavior

> Get-VM
WARNING: Please consider joining the VMware Customer Experience Improvement Program, so you can help us make PowerCLI a better product. You can join using the following command:

Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $true

VMware's Customer Experience Improvement Program ("CEIP") provides VMware with information that enables VMware to improve its products and services, to fix problems, and to advise you on how best to deploy and use our products.  As part of the CEIP, VMware collects technical information about your organization’s use of VMware products and services on a regular basis in association with your organization’s VMware license key(s).  This information does not personally identify any individual.

For more details: type "help about_ceip" to see the related help article.

To disable this warning and set your preference use the following command and restart PowerShell:
Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $true or $false.
Get-VM: 15/05/2023 17:49:56     Get-VM          You are not currently connected to any servers. Please connect first using a Connect cmdlet.

Error details

> Get-Error

Exception             :
    Type              : VMware.VimAutomation.Sdk.Types.V1.ErrorHandling.VimException.ViServerConnectionException
    ErrorId           : Core_BaseCmdlet_NotConnectedError
    ErrorCategory     : ResourceUnavailable
    RecommendedAction : Connect to server.
    Severity          : Error
    TargetSite        :
        Name          : ThrowNotConnectedError
        DeclaringType : VMware.VimAutomation.Sdk.Util10Ps.BaseCmdlet.MessageHelper
        MemberType    : Method
        Module        : VMware.VimAutomation.Sdk.Util10Ps.dll
    Message           : 15/05/2023 17:49:56     Get-VM          You are not currently connected to any servers. Please connect
first using a Connect cmdlet.
    Data              : System.Collections.ListDictionaryInternal
    Source            : VMware.VimAutomation.Sdk.Util10Ps
    HResult           : -2146232832
    StackTrace        :
   at VMware.VimAutomation.Sdk.Util10Ps.BaseCmdlet.MessageHelper.ThrowNotConnectedError()
   at VMware.VimAutomation.Sdk.Util10Ps.BaseCmdlet.ConnectionDispatchHelper.DispatchObjectsHelper(IList`1
connectionList, IList`1 moListFromCmdletParameter, Dictionary`2 runlist, Dictionary`2 connectionIdToUidBourneIdList,
Boolean isConnectionListExplicit)
   at VMware.VimAutomation.Sdk.Util10Ps.BaseCmdlet.ConnectionDispatchHelper.DispatchObjects(ParallelConnectionExecutor
executor, UidDispatchHelper uidDispatchHelper, IList`1 connectionList, Boolean isConnectionListExplicit, IList`1
moListFromCmdletParameter, String moListFromCmdletParameterName, Boolean isServerParameterObnOnly)
   at VMware.VimAutomation.Sdk.Util10Ps.BaseCmdlet.BaseCmdlet.ProcessRecordErrorHandled()
   at VMware.VimAutomation.ViCore.Util10Ps.BaseCmdlet.BaseCmdlet.ProcessRecordErrorHandled()
CategoryInfo          : ResourceUnavailable: (:) [Get-VM], ViServerConnectionException
FullyQualifiedErrorId : Core_BaseCmdlet_NotConnectedError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetVM
InvocationInfo        :
    MyCommand        : Get-VM
    ScriptLineNumber : 1
    OffsetInLine     : 1
    HistoryId        : 2
    Line             : Get-VM
    PositionMessage  : At line:1 char:1
                       + Get-VM
                       + ~~~~~~
    InvocationName   : Get-VM
    CommandOrigin    : Internal
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1

Environment data

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.3.4
PSEdition                      Core
GitCommitId                    7.3.4
OS                             Microsoft Windows 10.0.22621
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

Additional information

This is not the same issue as a function taking precedence over a cmdlet as documented. In this case both names refer to a cmdlet:

> Get-Command -Name "Get-VM" -All | ft -AutoSize

CommandType Name   Version         Source
----------- ----   -------         ------
Cmdlet      Get-VM 2.0.0.0         Hyper-V
Cmdlet      Get-VM 13.0.0.20797821 VMware.VimAutomation.Core

PowerShell decided to use VMware.VimAutomation.Core\Get-VM over Hyper-V\Get-VM contrary to reasonable exepctations. Perhaps because [Version]"13.0.0.20797821" -gt [Version]"2.0.0.0" or maybe because "VMware.VimAutomation.Core" -gt "Hyper-V".

I couldn’t find any documentation saying how the module auto-load feature assigns priorities in such a case so I don’t know if there even is a rule and it’s not arbitrary. Either way - assigning arbitrary priorities is unexpected and assigning the observed priorities is unexpected.

Perhaps it’s difficult to set a rule that would do the expected thing in all use cases. Allowing the user to set precedence of module load is probably the best approach.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 25 (11 by maintainers)

Most upvoted comments

What my reasons are is irrelevant. That’s why I said:

Either way - assigning arbitrary priorities is unexpected and assigning the observed priorities is unexpected.

$Env:PSModulePath says where to search for commands. By default the order is

  1. Modules installed for the current user
  2. Modules installed for all users of the current system
  3. Modules provided with PowerShell (for PowerShell 7)
  4. Models provided with the OS (and also with Windows PowerShell)

Even if you don’t agree with the order I think is correct, doing stuff randomly is unexpected and wrong.

Someone I worked with used to say “What the software does is not random, you just can’t see the pattern” . It is working from most specific definition of the command (user only) to most general (installed with the OS).

Consider what would happen if someone made a module with cmdlets such as Write-Warning and Select-Object. It is idiomatic not to specify their full names, so using them in every module can do anything under the approach you suggest.

Yes, and that is by design - although because they are in the utility module, that will often be loaded by something else. But if you take something like start-transcript you can replace it with your own start transcript command if you so choose. There have been comments in the past that if users can be tricked into installing a malicious module, then it can replace common commands with harmful ones (true) but the ability to replace a command is a key one.

I think the enhancement I describe would still be useful for module authors that want to ensure their commands are module qualified without having to import modules with -Force or having to create aliases or module qualifying all commands since the module author doesn’t know what commands are available on the system or loaded.

As for your alias suggestion I tried that as a workaround when this was brought up, that would work if your script requires Hyper-V but if you call a script that requires VMWare it would impact that script since aliases aren’t script scoped. I’m not aware of a way to scope the alias.

They’re scoped the same as any other session state item. So as long as the second script is not dot sourced, it can follow the same pattern of setting an alias at the top.

& {
    ${alias:Get-VM} = 'Hyper-V\Get-VM'
    (Get-Alias Get-VM).Definition
    & {
        ${alias:Get-VM} = 'VMware.VimAutomation.Core\Get-VM'
        (Get-Alias Get-VM).Definition
    }

    (Get-Alias Get-VM).Definition
}

# yields
# Hyper-V\Get-VM
# VMware.VimAutomation.Core\Get-VM
# Hyper-V\Get-VM

Set-Alias also has a -Scope parameter you can specify script to if desired.

  1. Lets say a using -commands Hyper-V is defined (note I don’t like naming things or don’t think using or commands is the best option just an example)

Wouldn’t Import-Module Hyper-V solve this? I guess if they’re both imported it can be problematic for specific commands but you can always do ${alias:Get-VM} = 'Hyper-V/Get-VM'

@kilasuit No, it does not but usually is good enough most of the time. As you mention and has been stated by others the only way is to use module qualified or by using prefixes.

There is one enhancement that could be implemented as brought up by the OP in this issue. The use of module qualified command names is tedious and not very pretty in scripts. What could be done is having a way in scripts to define that I’m using commands from this module as if they were module qualified.

How I see this working is as follows:

  1. Lets say a using -commands Hyper-V is defined (note I don’t like naming things or don’t think using or commands is the best option just an example)
  2. Then at runtime, the engine lookups all the commands shipped by Hyper-V and then any commands in the script from that module would be treated as module qualified.
  3. Any commands in the script that aren’t in the module are discovered as normal.