@@ -35,7 +35,7 @@ internal class DebugService
3535 private readonly ILogger _logger ;
3636 private readonly IInternalPowerShellExecutionService _executionService ;
3737 private readonly BreakpointService _breakpointService ;
38- private readonly RemoteFileManagerService remoteFileManager ;
38+ private readonly RemoteFileManagerService _remoteFileManager ;
3939
4040 private readonly PsesInternalHost _psesHost ;
4141
@@ -45,7 +45,7 @@ internal class DebugService
4545 private int nextVariableId ;
4646 private string temporaryScriptListingPath ;
4747 private List < VariableDetailsBase > variables ;
48- private VariableContainerDetails globalScopeVariables ;
48+ internal VariableContainerDetails globalScopeVariables ; // Internal for unit testing.
4949 private VariableContainerDetails scriptScopeVariables ;
5050 private VariableContainerDetails localScopeVariables ;
5151 private StackFrameDetails [ ] stackFrameDetails ;
@@ -80,28 +80,8 @@ internal class DebugService
8080
8181 /// <summary>
8282 /// Initializes a new instance of the DebugService class and uses
83- /// the given PowerShellContext for all future operations.
83+ /// the given execution service for all future operations.
8484 /// </summary>
85- /// <param name="powerShellContext">
86- /// The PowerShellContext to use for all debugging operations.
87- /// </param>
88- /// <param name="logger">An ILogger implementation used for writing log messages.</param>
89- //public DebugService(PowerShellContextService powerShellContext, ILogger logger)
90- // : this(powerShellContext, null, logger)
91- //{
92- //}
93-
94- /// <summary>
95- /// Initializes a new instance of the DebugService class and uses
96- /// the given PowerShellContext for all future operations.
97- /// </summary>
98- /// <param name="powerShellContext">
99- /// The PowerShellContext to use for all debugging operations.
100- /// </param>
101- //// <param name = "remoteFileManager" >
102- //// A RemoteFileManagerService instance to use for accessing files in remote sessions.
103- //// </param>
104- /// <param name="logger">An ILogger implementation used for writing log messages.</param>
10585 public DebugService (
10686 IInternalPowerShellExecutionService executionService ,
10787 IPowerShellDebugContext debugContext ,
@@ -120,8 +100,7 @@ public DebugService(
120100 _debugContext . DebuggerStopped += OnDebuggerStopAsync ;
121101 _debugContext . DebuggerResuming += OnDebuggerResuming ;
122102 _debugContext . BreakpointUpdated += OnBreakpointUpdated ;
123-
124- this . remoteFileManager = remoteFileManager ;
103+ _remoteFileManager = remoteFileManager ;
125104
126105 invocationTypeScriptPositionProperty =
127106 typeof ( InvocationInfo )
@@ -150,32 +129,27 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
150129
151130 string scriptPath = scriptFile . FilePath ;
152131 // Make sure we're using the remote script path
153- if ( _psesHost . CurrentRunspace . IsOnRemoteMachine && remoteFileManager is not null )
132+ if ( _psesHost . CurrentRunspace . IsOnRemoteMachine && _remoteFileManager is not null )
154133 {
155- if ( ! remoteFileManager . IsUnderRemoteTempPath ( scriptPath ) )
134+ if ( ! _remoteFileManager . IsUnderRemoteTempPath ( scriptPath ) )
156135 {
157136 _logger . LogTrace ( $ "Could not set breakpoints for local path '{ scriptPath } ' in a remote session.") ;
158-
159137 return Array . Empty < BreakpointDetails > ( ) ;
160138 }
161139
162- string mappedPath = remoteFileManager . GetMappedPath ( scriptPath , _psesHost . CurrentRunspace ) ;
163-
164- scriptPath = mappedPath ;
140+ scriptPath = _remoteFileManager . GetMappedPath ( scriptPath , _psesHost . CurrentRunspace ) ;
165141 }
166- else if ( temporaryScriptListingPath is not null
167- && temporaryScriptListingPath . Equals ( scriptPath , StringComparison . CurrentCultureIgnoreCase ) )
142+ else if ( temporaryScriptListingPath ? . Equals ( scriptPath , StringComparison . CurrentCultureIgnoreCase ) == true )
168143 {
169144 _logger . LogTrace ( $ "Could not set breakpoint on temporary script listing path '{ scriptPath } '.") ;
170-
171145 return Array . Empty < BreakpointDetails > ( ) ;
172146 }
173147
174148 // Fix for issue #123 - file paths that contain wildcard chars [ and ] need to
175149 // quoted and have those wildcard chars escaped.
176150 string escapedScriptPath = PathUtils . WildcardEscapePath ( scriptPath ) ;
177151
178- if ( dscBreakpoints is null || ! dscBreakpoints . IsDscResourcePath ( escapedScriptPath ) )
152+ if ( dscBreakpoints ? . IsDscResourcePath ( escapedScriptPath ) != true )
179153 {
180154 if ( clearExisting )
181155 {
@@ -185,10 +159,8 @@ public async Task<BreakpointDetails[]> SetLineBreakpointsAsync(
185159 return ( await _breakpointService . SetBreakpointsAsync ( escapedScriptPath , breakpoints ) . ConfigureAwait ( false ) ) . ToArray ( ) ;
186160 }
187161
188- return await dscBreakpoints . SetLineBreakpointsAsync (
189- _executionService ,
190- escapedScriptPath ,
191- breakpoints )
162+ return await dscBreakpoints
163+ . SetLineBreakpointsAsync ( _executionService , escapedScriptPath , breakpoints )
192164 . ConfigureAwait ( false ) ;
193165 }
194166
@@ -362,7 +334,7 @@ public VariableDetailsBase GetVariableFromExpression(string variableExpression)
362334 variableName ,
363335 StringComparison . CurrentCultureIgnoreCase ) ) ;
364336
365- if ( resolvedVariable is not null && resolvedVariable . IsExpandable )
337+ if ( resolvedVariable ? . IsExpandable == true )
366338 {
367339 // Continue by searching in this variable's children.
368340 variableList = GetVariables ( resolvedVariable . Id ) ;
@@ -383,6 +355,7 @@ public VariableDetailsBase GetVariableFromExpression(string variableExpression)
383355 /// <param name="value">The new string value. This value must not be null. If you want to set the variable to $null
384356 /// pass in the string "$null".</param>
385357 /// <returns>The string representation of the value the variable was set to.</returns>
358+ /// <exception cref="InvalidPowerShellExpressionException"></exception>
386359 public async Task < string > SetVariableAsync ( int variableContainerReferenceId , string name , string value )
387360 {
388361 Validate . IsNotNull ( nameof ( name ) , name ) ;
@@ -476,20 +449,18 @@ public async Task<string> SetVariableAsync(int variableContainerReferenceId, str
476449 {
477450 _logger . LogTrace ( $ "Setting variable '{ name } ' using conversion to value: { expressionResult ?? "<null>" } ") ;
478451
479- psVariable . Value = await _executionService . ExecuteDelegateAsync < object > (
452+ psVariable . Value = await _executionService . ExecuteDelegateAsync (
480453 "PS debugger argument converter" ,
481454 ExecutionOptions . Default ,
482- ( pwsh , cancellationToken ) =>
455+ ( pwsh , _ ) =>
483456 {
484457 var engineIntrinsics = ( EngineIntrinsics ) pwsh . Runspace . SessionStateProxy . GetVariable ( "ExecutionContext" ) ;
485458
486459 // TODO: This is almost (but not quite) the same as LanguagePrimitives.Convert(), which does not require the pipeline thread.
487460 // We should investigate changing it.
488461 return argTypeConverterAttr . Transform ( engineIntrinsics , expressionResult ) ;
489-
490462 } ,
491463 CancellationToken . None ) . ConfigureAwait ( false ) ;
492-
493464 }
494465 else
495466 {
@@ -634,17 +605,14 @@ private async Task FetchStackFramesAndVariablesAsync(string scriptNameOverride)
634605 nextVariableId = VariableDetailsBase . FirstVariableId ;
635606 variables = new List < VariableDetailsBase >
636607 {
637-
638608 // Create a dummy variable for index 0, should never see this.
639609 new VariableDetails ( "Dummy" , null )
640610 } ;
641611
642612 // Must retrieve in order of broadest to narrowest scope for efficient
643613 // deduplication: global, script, local.
644614 globalScopeVariables = await FetchVariableContainerAsync ( VariableContainerDetails . GlobalScopeName ) . ConfigureAwait ( false ) ;
645-
646615 scriptScopeVariables = await FetchVariableContainerAsync ( VariableContainerDetails . ScriptScopeName ) . ConfigureAwait ( false ) ;
647-
648616 localScopeVariables = await FetchVariableContainerAsync ( VariableContainerDetails . LocalScopeName ) . ConfigureAwait ( false ) ;
649617
650618 await FetchStackFramesAsync ( scriptNameOverride ) . ConfigureAwait ( false ) ;
@@ -662,9 +630,7 @@ private Task<VariableContainerDetails> FetchVariableContainerAsync(string scope)
662630
663631 private async Task < VariableContainerDetails > FetchVariableContainerAsync ( string scope , bool autoVarsOnly )
664632 {
665- PSCommand psCommand = new PSCommand ( )
666- . AddCommand ( "Get-Variable" )
667- . AddParameter ( "Scope" , scope ) ;
633+ PSCommand psCommand = new PSCommand ( ) . AddCommand ( "Get-Variable" ) . AddParameter ( "Scope" , scope ) ;
668634
669635 var scopeVariableContainer = new VariableContainerDetails ( nextVariableId ++ , "Scope: " + scope ) ;
670636 variables . Add ( scopeVariableContainer ) ;
@@ -674,17 +640,13 @@ private async Task<VariableContainerDetails> FetchVariableContainerAsync(string
674640 {
675641 results = await _executionService . ExecutePSCommandAsync < PSObject > ( psCommand , CancellationToken . None ) . ConfigureAwait ( false ) ;
676642 }
677- catch ( CmdletInvocationException ex )
643+ // It's possible to be asked to run `Get-Variable -Scope N` where N is a number that
644+ // exceeds the available scopes. In this case, the command throws this exception, but
645+ // there's nothing we can do about it, nor can we know the number of scopes that exist,
646+ // and we shouldn't crash the debugger, so we just return no results instead. All other
647+ // exceptions should be thrown again.
648+ catch ( CmdletInvocationException ex ) when ( ex . ErrorRecord . CategoryInfo . Reason . Equals ( "PSArgumentOutOfRangeException" ) )
678649 {
679- // It's possible to be asked to run `Get-Variable -Scope N` where N is a number that
680- // exceeds the available scopes. In this case, the command throws this exception,
681- // but there's nothing we can do about it, nor can we know the number of scopes that
682- // exist, and we shouldn't crash the debugger, so we just return no results instead.
683- // All other exceptions should be thrown again.
684- if ( ! ex . ErrorRecord . CategoryInfo . Reason . Equals ( "PSArgumentOutOfRangeException" ) )
685- {
686- throw ;
687- }
688650 results = null ;
689651 }
690652
@@ -749,8 +711,8 @@ private static bool ShouldAddAsVariable(VariableInfo variableInfo)
749711 {
750712 // Filter built-in constant or readonly variables like $true, $false, $null, etc.
751713 ScopedItemOptions variableScope = variableInfo . Variable . Options ;
752- var constantAllScope = ScopedItemOptions . AllScope | ScopedItemOptions . Constant ;
753- var readonlyAllScope = ScopedItemOptions . AllScope | ScopedItemOptions . ReadOnly ;
714+ const ScopedItemOptions constantAllScope = ScopedItemOptions . AllScope | ScopedItemOptions . Constant ;
715+ const ScopedItemOptions readonlyAllScope = ScopedItemOptions . AllScope | ScopedItemOptions . ReadOnly ;
754716 if ( ( ( variableScope & constantAllScope ) == constantAllScope )
755717 || ( ( variableScope & readonlyAllScope ) == readonlyAllScope ) )
756718 {
@@ -909,11 +871,11 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
909871 stackFrameDetailsEntry . ScriptPath = scriptNameOverride ;
910872 }
911873 else if ( isOnRemoteMachine
912- && remoteFileManager is not null
874+ && _remoteFileManager is not null
913875 && ! string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
914876 {
915877 stackFrameDetailsEntry . ScriptPath =
916- remoteFileManager . GetMappedPath ( stackFrameScriptPath , _psesHost . CurrentRunspace ) ;
878+ _remoteFileManager . GetMappedPath ( stackFrameScriptPath , _psesHost . CurrentRunspace ) ;
917879 }
918880
919881 stackFrameDetailList . Add ( stackFrameDetailsEntry ) ;
@@ -956,7 +918,7 @@ internal async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e)
956918 string localScriptPath = e . InvocationInfo . ScriptName ;
957919
958920 // If there's no ScriptName, get the "list" of the current source
959- if ( remoteFileManager is not null && string . IsNullOrEmpty ( localScriptPath ) )
921+ if ( _remoteFileManager is not null && string . IsNullOrEmpty ( localScriptPath ) )
960922 {
961923 // Get the current script listing and create the buffer
962924 PSCommand command = new PSCommand ( ) . AddScript ( $ "list 1 { int . MaxValue } ") ;
@@ -977,7 +939,7 @@ await _executionService.ExecutePSCommandAsync<PSObject>(
977939 . Where ( s => s is not null ) ) ;
978940
979941 temporaryScriptListingPath =
980- remoteFileManager . CreateTemporaryFile (
942+ _remoteFileManager . CreateTemporaryFile (
981943 $ "[{ _psesHost . CurrentRunspace . SessionDetails . ComputerName } ] { TemporaryScriptFileName } ",
982944 scriptListing ,
983945 _psesHost . CurrentRunspace ) ;
@@ -1000,19 +962,18 @@ await _executionService.ExecutePSCommandAsync<PSObject>(
1000962 // If this is a remote connection and the debugger stopped at a line
1001963 // in a script file, get the file contents
1002964 if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
1003- && remoteFileManager is not null
965+ && _remoteFileManager is not null
1004966 && ! noScriptName )
1005967 {
1006968 localScriptPath =
1007- await remoteFileManager . FetchRemoteFileAsync (
969+ await _remoteFileManager . FetchRemoteFileAsync (
1008970 e . InvocationInfo . ScriptName ,
1009971 _psesHost . CurrentRunspace ) . ConfigureAwait ( false ) ;
1010972 }
1011973
1012974 if ( stackFrameDetails . Length > 0 )
1013975 {
1014976 // Augment the top stack frame with details from the stop event
1015-
1016977 if ( invocationTypeScriptPositionProperty . GetValue ( e . InvocationInfo ) is IScriptExtent scriptExtent )
1017978 {
1018979 stackFrameDetails [ 0 ] . StartLineNumber = scriptExtent . StartLineNumber ;
@@ -1054,9 +1015,9 @@ private void OnBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e)
10541015 // TODO: This could be either a path or a script block!
10551016 string scriptPath = lineBreakpoint . Script ;
10561017 if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
1057- && remoteFileManager is not null )
1018+ && _remoteFileManager is not null )
10581019 {
1059- string mappedPath = remoteFileManager . GetMappedPath ( scriptPath , _psesHost . CurrentRunspace ) ;
1020+ string mappedPath = _remoteFileManager . GetMappedPath ( scriptPath , _psesHost . CurrentRunspace ) ;
10601021
10611022 if ( mappedPath is null )
10621023 {
0 commit comments