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
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 55.5k
- Forks
- 8.5k
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 88
Description
Prerequisites
- Write a descriptive title.
- Make sure you are able to repro it on the latest released version
- Search the existing issues.
- Refer to the FAQ.
- Refer to Differences between Windows PowerShell 5.1 and PowerShell.
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.
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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