Repository navigation
CodeCompletion exposes cmdlets not available to the parent runspace #1281
Description
Summary
When calling PSES programmatically (from C# but using the supported script) in a ConstrainedRunspace in ConstrainedLanguage mode, information about all cmdlets and the underlying filesystem is exposed via CodeCompletion.
Steps to Reproduce
StartLanguageServerViaScript()
{
InitialSessionState iss = InitialSessionState.Create();
iss.LanguageMode = PSLanguageMode.ConstrainedLanguage;
iss.ImportPSModule("Microsoft.Powershell.Utility", "Microsoft.Powershell.Core", "Microsoft.PowerShell.Security");
iss.Commands.Add(new SessionStateCmdletEntry("Get-Command", typeof(GetCommandCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("Where-Object", typeof(WhereObjectCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("Select-Object", typeof(SelectObjectCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("ForEach-Object", typeof(ForEachObjectCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("Format-List", typeof(FormatListCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("Format-Table", typeof(FormatTableCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("Set-ExecutionPolicy", typeof(SetExecutionPolicyCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("Format-Hex", typeof(FormatHex), null));
iss.Commands.Add(new SessionStateCmdletEntry("Out-Default", typeof(OutDefaultCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("Out-Host", typeof(OutHostCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("Import-Module", typeof(ImportModuleCommand), null));
iss.Commands.Add(new SessionStateCmdletEntry("Start-EditorServices", typeof(PSCommand), null));
iss.Commands.Add(new SessionStateFunctionEntry("TabExpansion2", PowershellCommands.TabExpansion));
iss.ImportPSModule(_modulePath);
Runspace myRunSpace = RunspaceFactory.CreateRunspace(_host, iss);
myRunSpace.Open();
PowerShell powershell = PowerShell.Create();
powershell.Runspace = myRunSpace;
powershell.AddCommand("Set-ExecutionPolicy")
.AddParameter("-ExecutionPolicy", "Bypass")
.AddParameter("-Scope", "Process")
.Invoke();
PSCommand[] profileCommands = PSHostUtilities.GetProfileCommands("Microsoft.PowerShell");
foreach (PSCommand command in profileCommands)
{
powershell.Commands = command;
powershell.Invoke();
}
powershell.AddScript(PowershellCommands.TabExpansion);
powershell.Runspace.SessionStateProxy.InvokeCommand.GetCommand("TabExpansion2", CommandTypes.Function);
var scriptInvocation = powershell.Runspace.SessionStateProxy.InvokeCommand
.GetCommand(_StartEditorServicesScriptPath, CommandTypes.ExternalScript);
powershell.AddCommand(scriptInvocation)
//"-FeatureFlags", "@()"
.AddParameter("HostName", "monaco")
.AddParameter("HostProfileId", "0")
.AddParameter("HostVersion", "1.0.0")
.AddParameter("LogPath", _logPath)
.AddParameter("LogLevel", "Diagnostic")
.AddParameter("BundledModulesPath", _bundledModulesPath)
.AddParameter("SessionDetailsPath", _sessionInfoFilePath)
.AddParameter("EnableConsoleRepl");
try
{
foreach (var command in powershell.Commands.Commands)
{
command.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
command.MergeMyResults(PipelineResultTypes.Information, PipelineResultTypes.Output);
}
_psInvocationResult = powershell.BeginInvoke();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Expected Behavior
CodeCompletion should only return information about the specific set of modules I loaded into my runspace via InitialSessionState
What actually happens
All available modules are available via CodeCompletion and my custom (and likely unnecessary) TabExpansion2 function never gets called.
Possible Fixes
Instead of always creating a new runspace in PowerShellContext.Create() use the existing one.
Line 216 in d54bda7
| Runspace initialRunspace = PowerShellContextService.CreateRunspace(psHost, hostStartupInfo.LanguageMode); |
Create a new runspace but allow the user to specify a custom initialsessionstate. Perhaps do this instead of the recent change that simply passes in LanguageMode as this would give the user more granular control of the runspace. This would naturally require Start-EditorServices to be modified to accept an initiationsessionstate object.
I feel pretty confident I can implement the required changes, I’d like some feedback as to which option is most feasible or if there are perhaps intentional reasons a new runspace is being created.
Activity
TylerLeonhardt commentedon May 2, 2020
Darren Kattan (@dkattan) I'm curious where you saw that this is the behavior of tab completion in CLM...
For me
TabExpansion2still completes everything in CLM.dkattan commentedon May 2, 2020
I think it’s worth noting that there is be a difference between ConstrainedLanguage mode and a Constrained Runspace.
https://docs.microsoft.com/en-us/powershell/scripting/developer/hosting/creating-a-constrained-runspace?view=powershell-7
A constrained runspace essentially has no cmdlets except for the ones explicitly added. It’s created using InitialSessionState.Create() instead of InitialSessionState.CreateDefault() or CreateDefault2(). There seems to be no support for .Create(), there is however an environmental variable to use CreateDefault instead of CreateDefault2.
That being said, I think it’s a reasonable expectation for PSES to not return information about cmdlets not available in the Constrained Runspace it was spawned from.
dkattan commentedon May 2, 2020
dkattan commentedon May 4, 2020
I began implementing a potential solution in #1282
I decided not to allow the end user to specify InitialSessionState as a separate parameter to Start-EditorServices, but instead infer it from Runspace.DefaultRunspace.InitialSessionState. Since InitialSessionState contains LanguageMode I simply replaced all the places TylerLeonhardt was referencing LanguageMode and used the full InitialSessionState object instead.