22// Licensed under the MIT License.
33
44using System . Collections . Generic ;
5+ using System . Linq ;
56using System . Management . Automation ;
7+ using System . Management . Automation . Runspaces ;
68using System . Threading ;
79using System . Threading . Tasks ;
810using MediatR ;
@@ -14,7 +16,37 @@ namespace Microsoft.PowerShell.EditorServices.Handlers
1416 [ Serial , Method ( "powerShell/getCommand" , Direction . ClientToServer ) ]
1517 internal interface IGetCommandHandler : IJsonRpcRequestHandler < GetCommandParams , List < PSCommandMessage > > { }
1618
17- internal class GetCommandParams : IRequest < List < PSCommandMessage > > { }
19+ internal class GetCommandParams : IRequest < List < PSCommandMessage > >
20+ {
21+ /// <summary>
22+ /// An optional name (supports wildcards) to scope the returned commands.
23+ /// When omitted, all commands are returned.
24+ /// </summary>
25+ public string Name { get ; set ; }
26+
27+ /// <summary>
28+ /// An optional module name (supports wildcards) to scope the returned
29+ /// commands. When omitted, commands from all modules are returned.
30+ /// </summary>
31+ public string Module { get ; set ; }
32+
33+ /// <summary>
34+ /// When true, the expensive parameter and parameter-set metadata is not
35+ /// resolved or returned. Callers that only need command names and modules
36+ /// (such as the Command Explorer tree) should set this to avoid the large
37+ /// serialization cost of the full command table.
38+ /// </summary>
39+ public bool ExcludeParameters { get ; set ; }
40+
41+ /// <summary>
42+ /// When true, module-less functions and scripts that PowerShell's default
43+ /// session provides (e.g. cd.., prompt, TabExpansion2) are omitted. These
44+ /// are interactive shell conveniences and engine plumbing rather than
45+ /// commands a user authored or imported, so the Command Explorer hides them.
46+ /// Module-provided commands (including built-in modules) are never affected.
47+ /// </summary>
48+ public bool ExcludeDefaultFunctions { get ; set ; }
49+ }
1850
1951 /// <summary>
2052 /// Describes the message to get the details for a single PowerShell Command
@@ -24,6 +56,7 @@ internal class PSCommandMessage
2456 {
2557 public string Name { get ; set ; }
2658 public string ModuleName { get ; set ; }
59+ public string ModuleVersion { get ; set ; }
2760 public string DefaultParameterSet { get ; set ; }
2861 public Dictionary < string , ParameterMetadata > Parameters { get ; set ; }
2962 public System . Collections . ObjectModel . ReadOnlyCollection < CommandParameterSetInfo > ParameterSets { get ; set ; }
@@ -39,11 +72,28 @@ public async Task<List<PSCommandMessage>> Handle(GetCommandParams request, Cance
3972 {
4073 PSCommand psCommand = new ( ) ;
4174
42- // Executes the following:
43- // Get-Command -CommandType Function,Cmdlet,ExternalScript | Sort-Object -Property Name
75+ // Executes the following, scoping by name and/or module when provided
76+ // so we don't serialize the entire command table (which is expensive):
77+ // Get-Command -CommandType Function,Cmdlet,ExternalScript [-Name <name>] [-Module <module>] | Sort-Object -Property Name
4478 psCommand
4579 . AddCommand ( @"Microsoft.PowerShell.Core\Get-Command" )
46- . AddParameter ( "CommandType" , new [ ] { "Function" , "Cmdlet" , "ExternalScript" } )
80+ . AddParameter ( "CommandType" , new [ ] { "Function" , "Cmdlet" , "ExternalScript" } ) ;
81+
82+ if ( ! string . IsNullOrEmpty ( request . Name ) )
83+ {
84+ psCommand . AddParameter ( "Name" , request . Name ) ;
85+ }
86+
87+ if ( ! string . IsNullOrEmpty ( request . Module ) )
88+ {
89+ psCommand . AddParameter ( "Module" , request . Module ) ;
90+ }
91+
92+ // A name or module filter that matches nothing writes a non-terminating
93+ // error; ignore it so we simply return an empty list instead.
94+ psCommand . AddParameter ( "ErrorAction" , "Ignore" ) ;
95+
96+ psCommand
4797 . AddCommand ( @"Microsoft.PowerShell.Utility\Sort-Object" )
4898 . AddParameter ( "Property" , "Name" ) ;
4999
@@ -54,6 +104,39 @@ public async Task<List<PSCommandMessage>> Handle(GetCommandParams request, Cance
54104 {
55105 foreach ( CommandInfo command in result )
56106 {
107+ // Skip commands injected by the editor's terminal integration
108+ // (the PSES host's fake PSConsoleHostReadLine and VS Code's
109+ // shell-integration helpers); they are implementation details,
110+ // not real commands the user authored or imported.
111+ if ( IsEditorInjectedCommand ( command ) )
112+ {
113+ continue ;
114+ }
115+
116+ // Optionally drop PowerShell's default-session shell functions
117+ // (and the install's profile-resource script), which are
118+ // module-less and not meaningful in the command list.
119+ if ( request . ExcludeDefaultFunctions
120+ && IsDefaultSessionFunction ( command ) )
121+ {
122+ continue ;
123+ }
124+
125+ // When only names/modules are requested, skip resolving the
126+ // parameter metadata entirely. Accessing Parameters/ParameterSets
127+ // forces PowerShell to compute (and we then serialize) the full
128+ // metadata, which is the dominant cost for the whole command table.
129+ if ( request . ExcludeParameters )
130+ {
131+ commandList . Add ( new PSCommandMessage
132+ {
133+ Name = command . Name ,
134+ ModuleName = command . ModuleName ,
135+ ModuleVersion = command . Version ? . ToString ( )
136+ } ) ;
137+ continue ;
138+ }
139+
57140 // Some info objects have a quicker way to get the DefaultParameterSet. These
58141 // are also the most likely to show up so win-win.
59142 string defaultParameterSet = null ;
@@ -84,6 +167,7 @@ public async Task<List<PSCommandMessage>> Handle(GetCommandParams request, Cance
84167 {
85168 Name = command . Name ,
86169 ModuleName = command . ModuleName ,
170+ ModuleVersion = command . Version ? . ToString ( ) ,
87171 Parameters = command . Parameters ,
88172 ParameterSets = command . ParameterSets ,
89173 DefaultParameterSet = defaultParameterSet
@@ -93,5 +177,74 @@ public async Task<List<PSCommandMessage>> Handle(GetCommandParams request, Cance
93177
94178 return commandList ;
95179 }
180+
181+ // Names of helper functions injected by VS Code's terminal shell
182+ // integration script (shellIntegration.ps1), which the PSES host executes.
183+ // These are editor plumbing rather than user- or module-provided commands.
184+ private static readonly HashSet < string > s_shellIntegrationFunctions = new ( System . StringComparer . OrdinalIgnoreCase )
185+ {
186+ "__VSCode-Escape-Value" ,
187+ "Set-MappedKeyHandler" ,
188+ "Set-MappedKeyHandlers"
189+ } ;
190+
191+ // Identifies commands injected by the editor's terminal integration that
192+ // should not be surfaced as real commands.
193+ private static bool IsEditorInjectedCommand ( CommandInfo command )
194+ {
195+ if ( command . CommandType != CommandTypes . Function )
196+ {
197+ return false ;
198+ }
199+
200+ // The fake global PSConsoleHostReadLine function that the PSES host
201+ // defines for terminal shell integration (see PsesInternalHost.cs) has
202+ // no real version, whereas the genuine PSReadLine export always reports
203+ // a real version, so that export is never matched here.
204+ if ( command . Name == "PSConsoleHostReadLine"
205+ && ( command . Version is null
206+ || ( command . Version . Major == 0
207+ && command . Version . Minor == 0
208+ && command . Version . Build <= 0
209+ && command . Version . Revision <= 0 ) ) )
210+ {
211+ return true ;
212+ }
213+
214+ return s_shellIntegrationFunctions . Contains ( command . Name ) ;
215+ }
216+
217+ // The names of the functions that PowerShell's default session state
218+ // provides (cd.., cd\, cd~, Clear-Host, exec, help, oss, Pause, prompt,
219+ // TabExpansion2). Enumerated once from InitialSessionState so the list stays
220+ // correct across PowerShell versions rather than being hard-coded.
221+ private static readonly System . Lazy < HashSet < string > > s_defaultSessionFunctions = new ( ( ) =>
222+ new HashSet < string > (
223+ InitialSessionState . CreateDefault2 ( ) . Commands
224+ . OfType < SessionStateFunctionEntry > ( )
225+ . Select ( static entry => entry . Name ) ,
226+ System . StringComparer . OrdinalIgnoreCase ) ) ;
227+
228+ // Identifies module-less functions and scripts that PowerShell's default
229+ // session provides — interactive shell conveniences and engine plumbing that
230+ // aren't meaningful in the command list. Only matches commands with no module,
231+ // so a module-provided command (including built-in modules) is never affected.
232+ private static bool IsDefaultSessionFunction ( CommandInfo command )
233+ {
234+ if ( ! string . IsNullOrEmpty ( command . ModuleName ) )
235+ {
236+ return false ;
237+ }
238+
239+ // The profile-resource script shipped alongside the PowerShell install
240+ // (e.g. pwsh.profile.resource.ps1) is install plumbing, not a user script.
241+ if ( command . CommandType == CommandTypes . ExternalScript )
242+ {
243+ return command . Name . StartsWith ( "pwsh.profile.resource" , System . StringComparison . OrdinalIgnoreCase ) ;
244+ }
245+
246+ return command . CommandType == CommandTypes . Function
247+ && s_defaultSessionFunctions . Value . Contains ( command . Name ) ;
248+ }
96249 }
97250}
0 commit comments