diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 0002245cb..e36ee83c2 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -7,7 +7,9 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; using System.Reflection; +using System.Security.AccessControl; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -451,22 +453,34 @@ public async Task SetVariableAsync(int variableContainerReferenceId, str // PSVariable *is* strongly typed, so we have to convert it. _logger.LogTrace($"Setting variable '{name}' using conversion to value: {expressionResult ?? ""}"); - // NOTE: We use 'Get-Variable' here instead of 'SessionStateProxy.GetVariable()' - // because we already have a pipeline running (the debugger) and the latter cannot - // run concurrently (threw 'NoSessionStateProxyWhenPipelineInProgress'). - IReadOnlyList results = await _executionService.ExecutePSCommandAsync( - new PSCommand() - .AddCommand(@"Microsoft.PowerShell.Utility\Get-Variable") - .AddParameter("Name", "ExecutionContext") - .AddParameter("ValueOnly"), - CancellationToken.None).ConfigureAwait(false); - EngineIntrinsics engineIntrinsics = results.Count > 0 - ? results[0] - : throw new Exception("Couldn't get EngineIntrinsics!"); + psVariable.Value = await _executionService.ExecuteDelegateAsync( + "PS debugger argument converter", + null, + (pwsh, _) => + { + // TODO: We want to replace the Get-Variable PSCommands with reflection to use the APIs in PowerShell, kind of like this: + // $ec = $host.GetType().GetMember("Context","NonPublic,Instance").GetMethod.Invoke($host, $null) + // $iso = $ec.gettype().GetMember("TopLevelSessionState", "Instance,NonPublic")[0].GetMethod.Invoke($ec, $null) + // ($iso.gettype().GetMembers("NonPublic,Instance") |?{$_.name - eq "GetVariable"})[1].Invoke($iso, @("PWD")).Value + // Then we can use like this API: + // https://cs.github.com/PowerShell/PowerShell/blob/397bd87cb30382c56ecdd1a715f05ed2855ca29e/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs#L616 + Runspace rs = RunspaceFactory.CreateRunspace(pwsh.Runspace.InitialSessionState); + + try + { + rs.Open(); + EngineIntrinsics engineIntrinsics = rs.SessionStateProxy.GetVariable("ExecutionContext") as EngineIntrinsics; - // TODO: This is almost (but not quite) the same as 'LanguagePrimitives.Convert()', - // which does not require the pipeline thread. We should investigate changing it. - psVariable.Value = argTypeConverterAttr.Transform(engineIntrinsics, expressionResult); + // TODO: This is almost (but not quite) the same as LanguagePrimitives.Convert(), which does not require the pipeline thread. + // We should investigate changing it. + return argTypeConverterAttr.Transform(engineIntrinsics, expressionResult); + } + finally + { + rs.Dispose(); + } + }, + CancellationToken.None).ConfigureAwait(false); } else { diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/HistoryTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/HistoryTest.ps1 new file mode 100644 index 000000000..a453c9618 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/HistoryTest.ps1 @@ -0,0 +1,5 @@ +Write-Host 1 +Write-Host 2 +Write-Host 3 +Write-Host 4 +Write-Host 5 diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 43101db9a..26ab8b336 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -536,6 +536,51 @@ await debugService.SetCommandBreakpointsAsync( Assert.Equal("\"True > \"", prompt.ValueString); } + [Fact] + public async Task DebugCommandsExcludedFromHistory() + { + ScriptFile testScript = GetDebugScript("HistoryTest.ps1"); + EvaluateHandler evaluateHandler = new(psesHost); + + string transcriptFile = Path.GetTempFileName(); + await debugService.EvaluateExpressionAsync($"Start-Transcript -UseMinimalHeader -LiteralPath {transcriptFile}", true).ConfigureAwait(true); + + BreakpointDetails[] breakpoints = await debugService.SetLineBreakpointsAsync( + testScript, + new[] + { + BreakpointDetails.Create(testScript.FilePath, 1), + BreakpointDetails.Create(testScript.FilePath, 4) + }).ConfigureAwait(true); + + Assert.Collection( + breakpoints, + (i) => { Assert.Equal(1, i.LineNumber); Assert.True(i.Verified); }, + (i) => { Assert.Equal(4, i.LineNumber); Assert.True(i.Verified); }); + + Task _ = ExecutePowerShellCommand(testScript.FilePath); + AssertDebuggerStopped(testScript.FilePath, 1); + debugService.Continue(); + AssertDebuggerStopped(testScript.FilePath, 4); + debugService.Continue(); + Thread.Sleep(1000); + + // await evaluateHandler.Handle( + // new EvaluateRequestArguments { Expression = "c", Context = "repl" }, + // CancellationToken.None).ConfigureAwait(true); + //AssertDebuggerStopped(testScript.FilePath, 4); + + // await evaluateHandler.Handle( + // new EvaluateRequestArguments { Expression = "q", Context = "repl" }, + // CancellationToken.None).ConfigureAwait(true); + + //debugService.Continue(); + + await debugService.EvaluateExpressionAsync("Stop-Transcript", true).ConfigureAwait(true); + string[] transcript = File.ReadAllLines(transcriptFile); + Assert.NotEmpty(transcript); + } + [SkippableFact] public async Task DebuggerBreaksInUntitledScript() {