1616using Microsoft . PowerShell . EditorServices . Services . PowerShell . Debugging ;
1717using Microsoft . PowerShell . EditorServices . Services . PowerShell . Execution ;
1818using Microsoft . PowerShell . EditorServices . Services . PowerShell . Host ;
19+ using Microsoft . PowerShell . EditorServices . Services . PowerShell . Utility ;
1920using Microsoft . PowerShell . EditorServices . Services . TextDocument ;
2021using Microsoft . PowerShell . EditorServices . Utility ;
2122
@@ -74,6 +75,15 @@ internal class DebugService
7475 /// </summary>
7576 public DebuggerStoppedEventArgs CurrentDebuggerStoppedEventArgs { get ; private set ; }
7677
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+
7787 #endregion
7888
7989 #region Constructors
@@ -128,6 +138,8 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
128138 DscBreakpointCapability dscBreakpoints = await _debugContext . GetDscBreakpointCapabilityAsync ( CancellationToken . None ) . ConfigureAwait ( false ) ;
129139
130140 string scriptPath = scriptFile . FilePath ;
141+
142+ _psesHost . Runspace . ThrowCancelledIfUnusable ( ) ;
131143 // Make sure we're using the remote script path
132144 if ( _psesHost . CurrentRunspace . IsOnRemoteMachine && _remoteFileManager is not null )
133145 {
@@ -771,22 +783,23 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
771783 const string callStackVarName = $ "$global:{ PsesGlobalVariableNamePrefix } CallStack";
772784 const string getPSCallStack = $ "Get-PSCallStack | ForEach-Object {{ [void]{ callStackVarName } .Add(@($PSItem, $PSItem.GetFrameVariables())) }}";
773785
786+ _psesHost . Runspace . ThrowCancelledIfUnusable ( ) ;
774787 // If we're attached to a remote runspace, we need to serialize the list prior to
775788 // 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
778791 // deserialize it.
779- bool isOnRemoteMachine = _psesHost . CurrentRunspace . IsOnRemoteMachine ;
780- string returnSerializedIfOnRemoteMachine = isOnRemoteMachine
792+ bool isRemoteRunspace = _psesHost . CurrentRunspace . Runspace . RunspaceIsRemote ;
793+ string returnSerializedIfInRemoteRunspace = isRemoteRunspace
781794 ? $ "[Management.Automation.PSSerializer]::Serialize({ callStackVarName } , 3)"
782795 : callStackVarName ;
783796
784797 // PSObject is used here instead of the specific type because we get deserialized
785798 // 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 } ") ;
787800 IReadOnlyList < PSObject > results = await _executionService . ExecutePSCommandAsync < PSObject > ( psCommand , CancellationToken . None ) . ConfigureAwait ( false ) ;
788801
789- IEnumerable callStack = isOnRemoteMachine
802+ IEnumerable callStack = isRemoteRunspace
790803 ? ( PSSerializer . Deserialize ( results [ 0 ] . BaseObject as string ) as PSObject ) ? . BaseObject as IList
791804 : results ;
792805
@@ -797,7 +810,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
797810 // We have to use reflection to get the variable dictionary.
798811 IList callStackFrameComponents = ( callStackFrameItem as PSObject ) ? . BaseObject as IList ;
799812 PSObject callStackFrame = callStackFrameComponents [ 0 ] as PSObject ;
800- IDictionary callStackVariables = isOnRemoteMachine
813+ IDictionary callStackVariables = isRemoteRunspace
801814 ? ( callStackFrameComponents [ 1 ] as PSObject ) ? . BaseObject as IDictionary
802815 : callStackFrameComponents [ 1 ] as IDictionary ;
803816
@@ -861,7 +874,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
861874 {
862875 stackFrameDetailsEntry . ScriptPath = scriptNameOverride ;
863876 }
864- else if ( isOnRemoteMachine
877+ else if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
865878 && _remoteFileManager is not null
866879 && ! string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
867880 {
@@ -905,83 +918,98 @@ private static string TrimScriptListingLine(PSObject scriptLineObj, ref int pref
905918
906919 internal async void OnDebuggerStopAsync ( object sender , DebuggerStopEventArgs e )
907920 {
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
913922 {
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 ;
916925
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 ) )
922928 {
923- int linePrefixLength = 0 ;
929+ // Get the current script listing and create the buffer
930+ PSCommand command = new PSCommand ( ) . AddScript ( $ "list 1 { int . MaxValue } ") ;
924931
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 ) ;
931935
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+ }
937964
938- localScriptPath =
939- temporaryScriptListingPath
940- ?? StackFrameDetails . NoFileScriptPath ;
965+ // Get call stack and variables.
966+ await FetchStackFramesAndVariablesAsync ( noScriptName ? localScriptPath : null ) . ConfigureAwait ( false ) ;
941967
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 ) ;
943978 }
944- else
979+
980+ if ( stackFrameDetails . Length > 0 )
945981 {
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+ }
947990 }
948- }
949991
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 ) ;
952997
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 )
9581002 {
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.
9631004 }
964-
965- if ( stackFrameDetails . Length > 0 )
1005+ catch ( Exception exception )
9661006 {
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 ) ;
9751012 }
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 ) ;
9851013 }
9861014
9871015 private void OnDebuggerResuming ( object sender , DebuggerResumingEventArgs debuggerResumingEventArgs ) => CurrentDebuggerStoppedEventArgs = null ;
0 commit comments