diff --git a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs index 383ecf4fa..7a1f4764e 100644 --- a/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs +++ b/src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -263,7 +264,30 @@ public VariableDetailsBase[] GetVariables(int variableReferenceId) VariableDetailsBase parentVariable = variables[variableReferenceId]; if (parentVariable.IsExpandable) { - childVariables = parentVariable.GetChildren(_logger); + // PERFORMANCE: Because of ToString calls that might be PS script properties, we want this + // to run on the pipeline thread to avoid retry timeouts + // TODO: Cancellation Token Maybe? + // TODO: Maybe pass + childVariables = _psesHost.InvokeDelegate + ( + representation: "GetChildren", + ExecutionOptions.Default, + (_) => + { + var existingRunspace = Runspace.DefaultRunspace; + try + { + Runspace.DefaultRunspace = _psesHost.CurrentRunspace.Runspace; + return parentVariable.GetChildren(_logger); + } + finally + { + Runspace.DefaultRunspace = existingRunspace; + } + }, + CancellationToken.None + ); + foreach (var child in childVariables) { // Only add child if it hasn't already been added. diff --git a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 index e8c23d9a0..8a327506d 100644 --- a/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 +++ b/test/PowerShellEditorServices.Test.Shared/Debugging/VariableTest.ps1 @@ -51,3 +51,21 @@ $sortedDictionary['blue'] = 'red' # This is a dummy function that the test will use to stop and evaluate the debug environment function __BreakDebuggerDerivedDictionaryPropertyInRawView{}; __BreakDebuggerDerivedDictionaryPropertyInRawView + + +class CustomToString { + [String]$String = 'Hello' + [String]ToString() { + return $this.String.ToUpper() + } +} +$SCRIPT:CustomToStrings = 1..1000 | ForEach-Object { + [CustomToString]::new() +} +$SCRIPT:Small = $SCRIPT:CustomToStrings[1..10] +$SCRIPT:Medium = $SCRIPT:CustomToStrings[1..50] +$SCRIPT:Large = $SCRIPT:CustomToStrings[1..100] + + +# This is a dummy function that the test will use to stop and evaluate the debug environment +function __BreakDebuggerToStringShouldMarshallToPipeline{}; __BreakDebuggerToStringShouldMarshallToPipeline diff --git a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs index 00996db96..bd3988d9c 100644 --- a/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs +++ b/test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs @@ -905,6 +905,26 @@ await debugService.SetLineBreakpointsAsync( Assert.Equal("73", childVars[1].ValueString); } + /// + /// Verifies Issue #1686 + /// + [Fact] + public async Task DebuggerToStringShouldMarshallToPipeline() + { + CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create("__BreakDebuggerToStringShouldMarshallToPipeline"); + await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true); + + // Execute the script and wait for the breakpoint to be hit + Task _ = ExecuteVariableScriptFile(); + AssertDebuggerStopped(commandBreakpointDetails: breakpoint); + + VariableDetailsBase variableArray = Array.Find( + GetVariables(VariableContainerDetails.ScriptScopeName), + v => v.Name == "$CustomToStrings" + ); + Assert.NotNull(variableArray); + } + // Verifies fix for issue #86, $proc = Get-Process foo displays just the ETS property set // and not all process properties. [Fact(Skip = "Length of child vars is wrong now")]