16
16
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Debugging ;
17
17
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Execution ;
18
18
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Host ;
19
+ using Microsoft . PowerShell . EditorServices . Services . PowerShell . Utility ;
19
20
using Microsoft . PowerShell . EditorServices . Services . TextDocument ;
20
21
using Microsoft . PowerShell . EditorServices . Utility ;
21
22
@@ -74,6 +75,15 @@ internal class DebugService
74
75
/// </summary>
75
76
public DebuggerStoppedEventArgs CurrentDebuggerStoppedEventArgs { get ; private set ; }
76
77
78
+ /// <summary>
79
+ /// Tracks whether we are running <c>Debug-Runspace</c> in an out-of-process runspace.
80
+ /// </summary>
81
+ public bool IsDebuggingRemoteRunspace
82
+ {
83
+ get => _debugContext . IsDebuggingRemoteRunspace ;
84
+ set => _debugContext . IsDebuggingRemoteRunspace = value ;
85
+ }
86
+
77
87
#endregion
78
88
79
89
#region Constructors
@@ -128,6 +138,8 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
128
138
DscBreakpointCapability dscBreakpoints = await _debugContext . GetDscBreakpointCapabilityAsync ( CancellationToken . None ) . ConfigureAwait ( false ) ;
129
139
130
140
string scriptPath = scriptFile . FilePath ;
141
+
142
+ _psesHost . Runspace . ThrowCancelledIfUnusable ( ) ;
131
143
// Make sure we're using the remote script path
132
144
if ( _psesHost . CurrentRunspace . IsOnRemoteMachine && _remoteFileManager is not null )
133
145
{
@@ -771,22 +783,23 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
771
783
const string callStackVarName = $ "$global:{ PsesGlobalVariableNamePrefix } CallStack";
772
784
const string getPSCallStack = $ "Get-PSCallStack | ForEach-Object {{ [void]{ callStackVarName } .Add(@($PSItem, $PSItem.GetFrameVariables())) }}";
773
785
786
+ _psesHost . Runspace . ThrowCancelledIfUnusable ( ) ;
774
787
// If we're attached to a remote runspace, we need to serialize the list prior to
775
788
// transport because the default depth is too shallow. From testing, we determined the
776
- // correct depth is 3. The script always calls `Get-PSCallStack`. On a local machine , we
777
- // just return its results. On a remote machine we serialize it first and then later
789
+ // correct depth is 3. The script always calls `Get-PSCallStack`. In a local runspace , we
790
+ // just return its results. In a remote runspace we serialize it first and then later
778
791
// deserialize it.
779
- bool isOnRemoteMachine = _psesHost . CurrentRunspace . IsOnRemoteMachine ;
780
- string returnSerializedIfOnRemoteMachine = isOnRemoteMachine
792
+ bool isRemoteRunspace = _psesHost . CurrentRunspace . Runspace . RunspaceIsRemote ;
793
+ string returnSerializedIfInRemoteRunspace = isRemoteRunspace
781
794
? $ "[Management.Automation.PSSerializer]::Serialize({ callStackVarName } , 3)"
782
795
: callStackVarName ;
783
796
784
797
// PSObject is used here instead of the specific type because we get deserialized
785
798
// objects from remote sessions and want a common interface.
786
- PSCommand psCommand = new PSCommand ( ) . AddScript ( $ "[Collections.ArrayList]{ callStackVarName } = @(); { getPSCallStack } ; { returnSerializedIfOnRemoteMachine } ") ;
799
+ PSCommand psCommand = new PSCommand ( ) . AddScript ( $ "[Collections.ArrayList]{ callStackVarName } = @(); { getPSCallStack } ; { returnSerializedIfInRemoteRunspace } ") ;
787
800
IReadOnlyList < PSObject > results = await _executionService . ExecutePSCommandAsync < PSObject > ( psCommand , CancellationToken . None ) . ConfigureAwait ( false ) ;
788
801
789
- IEnumerable callStack = isOnRemoteMachine
802
+ IEnumerable callStack = isRemoteRunspace
790
803
? ( PSSerializer . Deserialize ( results [ 0 ] . BaseObject as string ) as PSObject ) ? . BaseObject as IList
791
804
: results ;
792
805
@@ -797,7 +810,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
797
810
// We have to use reflection to get the variable dictionary.
798
811
IList callStackFrameComponents = ( callStackFrameItem as PSObject ) ? . BaseObject as IList ;
799
812
PSObject callStackFrame = callStackFrameComponents [ 0 ] as PSObject ;
800
- IDictionary callStackVariables = isOnRemoteMachine
813
+ IDictionary callStackVariables = isRemoteRunspace
801
814
? ( callStackFrameComponents [ 1 ] as PSObject ) ? . BaseObject as IDictionary
802
815
: callStackFrameComponents [ 1 ] as IDictionary ;
803
816
@@ -861,7 +874,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
861
874
{
862
875
stackFrameDetailsEntry . ScriptPath = scriptNameOverride ;
863
876
}
864
- else if ( isOnRemoteMachine
877
+ else if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
865
878
&& _remoteFileManager is not null
866
879
&& ! string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
867
880
{
@@ -905,83 +918,98 @@ private static string TrimScriptListingLine(PSObject scriptLineObj, ref int pref
905
918
906
919
internal async void OnDebuggerStopAsync ( object sender , DebuggerStopEventArgs e )
907
920
{
908
- bool noScriptName = false ;
909
- string localScriptPath = e . InvocationInfo . ScriptName ;
910
-
911
- // If there's no ScriptName, get the "list" of the current source
912
- if ( _remoteFileManager is not null && string . IsNullOrEmpty ( localScriptPath ) )
921
+ try
913
922
{
914
- // Get the current script listing and create the buffer
915
- PSCommand command = new PSCommand ( ) . AddScript ( $ "list 1 { int . MaxValue } " ) ;
923
+ bool noScriptName = false ;
924
+ string localScriptPath = e . InvocationInfo . ScriptName ;
916
925
917
- IReadOnlyList < PSObject > scriptListingLines =
918
- await _executionService . ExecutePSCommandAsync < PSObject > (
919
- command , CancellationToken . None ) . ConfigureAwait ( false ) ;
920
-
921
- if ( scriptListingLines is not null )
926
+ // If there's no ScriptName, get the "list" of the current source
927
+ if ( _remoteFileManager is not null && string . IsNullOrEmpty ( localScriptPath ) )
922
928
{
923
- int linePrefixLength = 0 ;
929
+ // Get the current script listing and create the buffer
930
+ PSCommand command = new PSCommand ( ) . AddScript ( $ "list 1 { int . MaxValue } ") ;
924
931
925
- string scriptListing =
926
- string . Join (
927
- Environment . NewLine ,
928
- scriptListingLines
929
- . Select ( o => TrimScriptListingLine ( o , ref linePrefixLength ) )
930
- . Where ( s => s is not null ) ) ;
932
+ IReadOnlyList < PSObject > scriptListingLines =
933
+ await _executionService . ExecutePSCommandAsync < PSObject > (
934
+ command , CancellationToken . None ) . ConfigureAwait ( false ) ;
931
935
932
- temporaryScriptListingPath =
933
- _remoteFileManager . CreateTemporaryFile (
934
- $ "[{ _psesHost . CurrentRunspace . SessionDetails . ComputerName } ] { TemporaryScriptFileName } ",
935
- scriptListing ,
936
- _psesHost . CurrentRunspace ) ;
936
+ if ( scriptListingLines is not null )
937
+ {
938
+ int linePrefixLength = 0 ;
939
+
940
+ string scriptListing =
941
+ string . Join (
942
+ Environment . NewLine ,
943
+ scriptListingLines
944
+ . Select ( o => TrimScriptListingLine ( o , ref linePrefixLength ) )
945
+ . Where ( s => s is not null ) ) ;
946
+
947
+ temporaryScriptListingPath =
948
+ _remoteFileManager . CreateTemporaryFile (
949
+ $ "[{ _psesHost . CurrentRunspace . SessionDetails . ComputerName } ] { TemporaryScriptFileName } ",
950
+ scriptListing ,
951
+ _psesHost . CurrentRunspace ) ;
952
+
953
+ localScriptPath =
954
+ temporaryScriptListingPath
955
+ ?? StackFrameDetails . NoFileScriptPath ;
956
+
957
+ noScriptName = localScriptPath is not null ;
958
+ }
959
+ else
960
+ {
961
+ _logger . LogWarning ( "Could not load script context" ) ;
962
+ }
963
+ }
937
964
938
- localScriptPath =
939
- temporaryScriptListingPath
940
- ?? StackFrameDetails . NoFileScriptPath ;
965
+ // Get call stack and variables.
966
+ await FetchStackFramesAndVariablesAsync ( noScriptName ? localScriptPath : null ) . ConfigureAwait ( false ) ;
941
967
942
- noScriptName = localScriptPath is not null ;
968
+ // If this is a remote connection and the debugger stopped at a line
969
+ // in a script file, get the file contents
970
+ if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
971
+ && _remoteFileManager is not null
972
+ && ! noScriptName )
973
+ {
974
+ localScriptPath =
975
+ await _remoteFileManager . FetchRemoteFileAsync (
976
+ e . InvocationInfo . ScriptName ,
977
+ _psesHost . CurrentRunspace ) . ConfigureAwait ( false ) ;
943
978
}
944
- else
979
+
980
+ if ( stackFrameDetails . Length > 0 )
945
981
{
946
- _logger . LogWarning ( "Could not load script context" ) ;
982
+ // Augment the top stack frame with details from the stop event
983
+ if ( invocationTypeScriptPositionProperty . GetValue ( e . InvocationInfo ) is IScriptExtent scriptExtent )
984
+ {
985
+ stackFrameDetails [ 0 ] . StartLineNumber = scriptExtent . StartLineNumber ;
986
+ stackFrameDetails [ 0 ] . EndLineNumber = scriptExtent . EndLineNumber ;
987
+ stackFrameDetails [ 0 ] . StartColumnNumber = scriptExtent . StartColumnNumber ;
988
+ stackFrameDetails [ 0 ] . EndColumnNumber = scriptExtent . EndColumnNumber ;
989
+ }
947
990
}
948
- }
949
991
950
- // Get call stack and variables.
951
- await FetchStackFramesAndVariablesAsync ( noScriptName ? localScriptPath : null ) . ConfigureAwait ( false ) ;
992
+ CurrentDebuggerStoppedEventArgs =
993
+ new DebuggerStoppedEventArgs (
994
+ e ,
995
+ _psesHost . CurrentRunspace ,
996
+ localScriptPath ) ;
952
997
953
- // If this is a remote connection and the debugger stopped at a line
954
- // in a script file, get the file contents
955
- if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
956
- && _remoteFileManager is not null
957
- && ! noScriptName )
998
+ // Notify the host that the debugger is stopped.
999
+ DebuggerStopped ? . Invoke ( sender , CurrentDebuggerStoppedEventArgs ) ;
1000
+ }
1001
+ catch ( OperationCanceledException )
958
1002
{
959
- localScriptPath =
960
- await _remoteFileManager . FetchRemoteFileAsync (
961
- e . InvocationInfo . ScriptName ,
962
- _psesHost . CurrentRunspace ) . ConfigureAwait ( false ) ;
1003
+ // Ignore, likely means that a remote runspace has closed.
963
1004
}
964
-
965
- if ( stackFrameDetails . Length > 0 )
1005
+ catch ( Exception exception )
966
1006
{
967
- // Augment the top stack frame with details from the stop event
968
- if ( invocationTypeScriptPositionProperty . GetValue ( e . InvocationInfo ) is IScriptExtent scriptExtent )
969
- {
970
- stackFrameDetails [ 0 ] . StartLineNumber = scriptExtent . StartLineNumber ;
971
- stackFrameDetails [ 0 ] . EndLineNumber = scriptExtent . EndLineNumber ;
972
- stackFrameDetails [ 0 ] . StartColumnNumber = scriptExtent . StartColumnNumber ;
973
- stackFrameDetails [ 0 ] . EndColumnNumber = scriptExtent . EndColumnNumber ;
974
- }
1007
+ // Log in a catch all so we don't crash the process.
1008
+ _logger . LogError (
1009
+ exception ,
1010
+ "Error occurred while obtaining debug info. Message: {message}" ,
1011
+ exception . Message ) ;
975
1012
}
976
-
977
- CurrentDebuggerStoppedEventArgs =
978
- new DebuggerStoppedEventArgs (
979
- e ,
980
- _psesHost . CurrentRunspace ,
981
- localScriptPath ) ;
982
-
983
- // Notify the host that the debugger is stopped.
984
- DebuggerStopped ? . Invoke ( sender , CurrentDebuggerStoppedEventArgs ) ;
985
1013
}
986
1014
987
1015
private void OnDebuggerResuming ( object sender , DebuggerResumingEventArgs debuggerResumingEventArgs ) => CurrentDebuggerStoppedEventArgs = null ;
0 commit comments