PowerShell / PowerShell/PowerShell

Running pwsh.exe in a certain way can cause a nested modules' functions to pollute the global scope breaking built-in cmdlets

Open
#24,688 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Needs-Triage WG-Engine WG-NeedsReview
Dominant language
C#
Stars
55.5k
Forks
8.5k
Avg merge
1d 2h
Merged PRs (30d)
88

Description

Prerequisites
Steps to reproduce

I was working on something this afternoon and ran into the strangest thing that I have encountered this year.

I've distilled this down to the least complicated way to replicate what I run into, but this was not the way that I wandered into the problem.

I was trying out the AzDevOps PowerShell module. It turned out to not being very useful for what I wanted, but for some reason has a nested module (called core ) that has a function that they called Get-Process .

A bit silly, yes, but should not be catastrophic, right?

Expected behavior
# install the AzDevOps module
PS C:\> install-module AzDevOps -scope CurrentUser -force

# run get-process in a new pwsh process with the case for the path different than what's on disk
PS C:\> & "c:\program Files\PowerShell\7\pwsh.exe"  -Command "(get-process).count"
504
Actual behavior
# install the AzDevOps module
PS C:\> install-module AzDevOps -scope CurrentUser -force

# run get-process in a new pwsh process with the case for the path different than what's on disk
PS C:\> & "c:\program Files\PowerShell\7\pwsh.exe"  -Command "(get-process).count"

get-process: The term 'get-process' 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.

#### HUH? ^^^^^^^^^

# Can also replicate with the following two commands:
install-module AzDevOps -force
& ((get-command pwsh).path).ToUpper() -command "(get-process).count"

get-process: The term 'get-process' 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.
Error details
PS C:\> & "c:\Program Files\PowerShell\7\pwsh"  -Command "(get-process).count ; get-error"
get-process: The term 'get-process' 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.

Exception             :
    Type        : System.Management.Automation.CommandNotFoundException
    ErrorRecord :
        Exception             :
            Type    : System.Management.Automation.ParentContainsErrorRecordException
            Message : The term 'get-process' 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.
            HResult : -2146233087
        TargetObject          : get-process
        CategoryInfo          : ObjectNotFound: (get-process:String) [], ParentContainsErrorRecordException
        FullyQualifiedErrorId : CommandNotFoundException
        InvocationInfo        :
            ScriptLineNumber : 1
            OffsetInLine     : 2
            HistoryId        : 1
            Line             : (get-process).count ; get-error
            Statement        : get-process
            PositionMessage  : At line:1 char:2
                               + (get-process).count ; get-error
                               +  ~~~~~~~~~~~
            InvocationName   : get-process
            CommandOrigin    : Internal
        ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
    CommandName : get-process
    TargetSite  :
        Name          : LookupCommandInfo
        DeclaringType : [System.Management.Automation.CommandDiscovery]
        MemberType    : Method
        Module        : System.Management.Automation.dll
    Message     : The term 'get-process' 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.
    Data        : System.Collections.ListDictionaryInternal
    Source      : System.Management.Automation
    HResult     : -2146233087
    StackTrace  :
   at System.Management.Automation.CommandDiscovery.LookupCommandInfo(String commandName, CommandTypes commandTypes, SearchResolutionOptions
searchResolutionOptions, CommandOrigin commandOrigin, ExecutionContext context)
   at System.Management.Automation.CommandDiscovery.TryModuleAutoDiscovery(String commandName, ExecutionContext context, String originalCommandName,
CommandOrigin commandOrigin, SearchResolutionOptions searchResolutionOptions, CommandTypes commandTypes, Exception& lastError)
   at System.Management.Automation.CommandDiscovery.LookupCommandInfo(String commandName, CommandTypes commandTypes, SearchResolutionOptions
searchResolutionOptions, CommandOrigin commandOrigin, ExecutionContext context)
   at System.Management.Automation.CommandDiscovery.LookupCommandProcessor(String commandName, CommandOrigin commandOrigin, Nullable`1 useLocalScope)
   at System.Management.Automation.ExecutionContext.CreateCommand(String command, Boolean dotSource)
   at System.Management.Automation.PipelineOps.AddCommand(PipelineProcessor pipe, CommandParameterInternal[] commandElements, CommandBaseAst
commandBaseAst, CommandRedirection[] redirections, ExecutionContext context)
   at System.Management.Automation.PipelineOps.InvokePipeline(Object input, Boolean ignoreInput, CommandParameterInternal[][] pipeElements,
CommandBaseAst[] pipeElementAsts, CommandRedirection[][] commandRedirections, FunctionContext funcContext)
   at System.Management.Automation.Interpreter.ActionCallInstruction`6.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
TargetObject          : get-process
CategoryInfo          : ObjectNotFound: (get-process:String) [], CommandNotFoundException
FullyQualifiedErrorId : CommandNotFoundException
InvocationInfo        :
    ScriptLineNumber : 1
    OffsetInLine     : 2
    HistoryId        : 1
    Line             : (get-process).count ; get-error
    Statement        : get-process
    PositionMessage  : At line:1 char:2
                       + (get-process).count ; get-error
                       +  ~~~~~~~~~~~
    InvocationName   : get-process
    CommandOrigin    : Internal
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
Environment data
Name                           Value
----                           -----
PSVersion                      7.4.6
PSEdition                      Core
GitCommitId                    7.4.6
OS                             Microsoft Windows 10.0.22631
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Visuals

But, if you start a pwsh.exe process where the path to the PowerShell process is not cased the same as the actual path is on disk, then ... the nested module's version of Get-Process will somehow break access to the actual Get-Process cmdlet.

image

Now, this isn't how I encountered this - I was writing a tool that used powershell behind the scenes to gather information about processes and whatnot, and I inadvertently ran into this.

Once you have run a pwsh.exe process that is 'tainted' (for lack of a better word) - the children of that process will be tainted too.

if you attempt to use get-process in the child process it will still be broken

Removing the AzDevOps module 'cures' the problem - but this should not be possible to break in this way.

image

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure on Windows using the AzDevOps module, a differently cased pwsh.exe path, and the (get-process).count command. Start from the command-discovery and module-auto-discovery entry points named in the stack trace. Done means Get-Process remains available in the new process and its children under the reported invocation, with the regression covered by an appropriate test.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, shell
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.