1212using System . Threading . Tasks ;
1313using Microsoft . PowerShell . EditorServices . Debugging ;
1414using Microsoft . PowerShell . EditorServices . Utility ;
15- using System . IO ;
1615using Microsoft . PowerShell . EditorServices . Session ;
1716
1817namespace Microsoft . PowerShell . EditorServices
@@ -26,6 +25,7 @@ public class DebugService
2625 #region Fields
2726
2827 private const string PsesGlobalVariableNamePrefix = "__psEditorServices_" ;
28+ private const string TemporaryScriptFileName = "TemporaryScript.ps1" ;
2929
3030 private PowerShellContext powerShellContext ;
3131 private RemoteFileManager remoteFileManager ;
@@ -35,6 +35,7 @@ public class DebugService
3535 new Dictionary < string , List < Breakpoint > > ( ) ;
3636
3737 private int nextVariableId ;
38+ private string temporaryScriptListingPath ;
3839 private List < VariableDetailsBase > variables ;
3940 private VariableContainerDetails globalScopeVariables ;
4041 private VariableContainerDetails scriptScopeVariables ;
@@ -93,8 +94,8 @@ public DebugService(
9394 /// <param name="clearExisting">If true, causes all existing breakpoints to be cleared before setting new ones.</param>
9495 /// <returns>An awaitable Task that will provide details about the breakpoints that were set.</returns>
9596 public async Task < BreakpointDetails [ ] > SetLineBreakpoints (
96- ScriptFile scriptFile ,
97- BreakpointDetails [ ] breakpoints ,
97+ ScriptFile scriptFile ,
98+ BreakpointDetails [ ] breakpoints ,
9899 bool clearExisting = true )
99100 {
100101 var resultBreakpointDetails = new List < BreakpointDetails > ( ) ;
@@ -111,22 +112,32 @@ public async Task<BreakpointDetails[]> SetLineBreakpoints(
111112 if ( this . powerShellContext . CurrentRunspace . Location == RunspaceLocation . Remote &&
112113 this . remoteFileManager != null )
113114 {
114- string mappedPath =
115- this . remoteFileManager . GetMappedPath (
116- scriptPath ,
117- this . powerShellContext . CurrentRunspace ) ;
118-
119- if ( mappedPath == null )
115+ if ( ! this . remoteFileManager . IsUnderRemoteTempPath ( scriptPath ) )
120116 {
121117 Logger . Write (
122- LogLevel . Error ,
123- $ "Could not map local path '{ scriptPath } ' to a remote path .") ;
118+ LogLevel . Verbose ,
119+ $ "Could not set breakpoints for local path '{ scriptPath } ' in a remote session .") ;
124120
125121 return resultBreakpointDetails . ToArray ( ) ;
126122 }
127123
124+ string mappedPath =
125+ this . remoteFileManager . GetMappedPath (
126+ scriptPath ,
127+ this . powerShellContext . CurrentRunspace ) ;
128+
128129 scriptPath = mappedPath ;
129130 }
131+ else if (
132+ this . temporaryScriptListingPath != null &&
133+ this . temporaryScriptListingPath . Equals ( scriptPath , StringComparison . CurrentCultureIgnoreCase ) )
134+ {
135+ Logger . Write (
136+ LogLevel . Verbose ,
137+ $ "Could not set breakpoint on temporary script listing path '{ scriptPath } '.") ;
138+
139+ return resultBreakpointDetails . ToArray ( ) ;
140+ }
130141
131142 // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to
132143 // quoted and have those wildcard chars escaped.
@@ -622,7 +633,7 @@ private async Task ClearCommandBreakpoints()
622633 await this . powerShellContext . ExecuteCommand < object > ( psCommand ) ;
623634 }
624635
625- private async Task FetchStackFramesAndVariables ( )
636+ private async Task FetchStackFramesAndVariables ( string scriptNameOverride )
626637 {
627638 this . nextVariableId = VariableDetailsBase . FirstVariableId ;
628639 this . variables = new List < VariableDetailsBase > ( ) ;
@@ -633,7 +644,7 @@ private async Task FetchStackFramesAndVariables()
633644 // Must retrieve global/script variales before stack frame variables
634645 // as we check stack frame variables against globals.
635646 await FetchGlobalAndScriptVariables ( ) ;
636- await FetchStackFrames ( ) ;
647+ await FetchStackFrames ( scriptNameOverride ) ;
637648 }
638649
639650 private async Task FetchGlobalAndScriptVariables ( )
@@ -750,7 +761,7 @@ private bool AddToAutoVariables(PSObject psvariable, string scope)
750761 return true ;
751762 }
752763
753- private async Task FetchStackFrames ( )
764+ private async Task FetchStackFrames ( string scriptNameOverride )
754765 {
755766 PSCommand psCommand = new PSCommand ( ) ;
756767
@@ -782,7 +793,12 @@ private async Task FetchStackFrames()
782793 StackFrameDetails . Create ( callStackFrames [ i ] , autoVariables , localVariables ) ;
783794
784795 string stackFrameScriptPath = this . stackFrameDetails [ i ] . ScriptPath ;
785- if ( this . powerShellContext . CurrentRunspace . Location == RunspaceLocation . Remote &&
796+ if ( scriptNameOverride != null &&
797+ string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
798+ {
799+ this . stackFrameDetails [ i ] . ScriptPath = scriptNameOverride ;
800+ }
801+ else if ( this . powerShellContext . CurrentRunspace . Location == RunspaceLocation . Remote &&
786802 this . remoteFileManager != null &&
787803 ! string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
788804 {
@@ -979,6 +995,25 @@ private string FormatInvalidBreakpointConditionMessage(string condition, string
979995 return $ "'{ condition } ' is not a valid PowerShell expression. { message } ";
980996 }
981997
998+ private string TrimScriptListingLine ( PSObject scriptLineObj , ref int prefixLength )
999+ {
1000+ string scriptLine = scriptLineObj . ToString ( ) ;
1001+
1002+ if ( ! string . IsNullOrWhiteSpace ( scriptLine ) )
1003+ {
1004+ if ( prefixLength == 0 )
1005+ {
1006+ // The prefix is a padded integer ending with ':', an asterisk '*'
1007+ // if this is the current line, and one character of padding
1008+ prefixLength = scriptLine . IndexOf ( ':' ) + 2 ;
1009+ }
1010+
1011+ return scriptLine . Substring ( prefixLength ) ;
1012+ }
1013+
1014+ return null ;
1015+ }
1016+
9821017 #endregion
9831018
9841019 #region Events
@@ -990,11 +1025,56 @@ private string FormatInvalidBreakpointConditionMessage(string condition, string
9901025
9911026 private async void OnDebuggerStop ( object sender , DebuggerStopEventArgs e )
9921027 {
1028+ bool noScriptName = false ;
1029+ string localScriptPath = e . InvocationInfo . ScriptName ;
1030+
1031+ // If there's no ScriptName, get the "list" of the current source
1032+ if ( this . remoteFileManager != null && string . IsNullOrEmpty ( localScriptPath ) )
1033+ {
1034+ // Get the current script listing and create the buffer
1035+ PSCommand command = new PSCommand ( ) ;
1036+ command . AddScript ( $ "list 1 { int . MaxValue } ") ;
1037+
1038+ IEnumerable < PSObject > scriptListingLines =
1039+ await this . powerShellContext . ExecuteCommand < PSObject > (
1040+ command , false , false ) ;
1041+
1042+ if ( scriptListingLines != null )
1043+ {
1044+ int linePrefixLength = 0 ;
1045+
1046+ string scriptListing =
1047+ string . Join (
1048+ Environment . NewLine ,
1049+ scriptListingLines
1050+ . Select ( o => this . TrimScriptListingLine ( o , ref linePrefixLength ) )
1051+ . Where ( s => s != null ) ) ;
1052+
1053+ this . temporaryScriptListingPath =
1054+ this . remoteFileManager . CreateTemporaryFile (
1055+ TemporaryScriptFileName ,
1056+ scriptListing ,
1057+ this . powerShellContext . CurrentRunspace ) ;
1058+
1059+ localScriptPath =
1060+ this . temporaryScriptListingPath
1061+ ?? StackFrameDetails . NoFileScriptPath ;
1062+
1063+ noScriptName = localScriptPath != null ;
1064+ }
1065+ else
1066+ {
1067+ Logger . Write (
1068+ LogLevel . Warning ,
1069+ $ "Could not load script context") ;
1070+ }
1071+ }
1072+
9931073 // Get call stack and variables.
994- await this . FetchStackFramesAndVariables ( ) ;
1074+ await this . FetchStackFramesAndVariables (
1075+ noScriptName ? localScriptPath : null ) ;
9951076
9961077 // If this is a remote connection, get the file content
997- string localScriptPath = e . InvocationInfo . ScriptName ;
9981078 if ( this . powerShellContext . CurrentRunspace . Location == RunspaceLocation . Remote &&
9991079 this . remoteFileManager != null )
10001080 {
0 commit comments