@@ -668,6 +668,11 @@ private async Task<VariableContainerDetails> FetchVariableContainerAsync(string
668668 }
669669 catch ( CmdletInvocationException ex )
670670 {
671+ // It's possible to be asked to run `Get-Variable -Scope N` where N is a number that
672+ // exceeds the available scopes. In this case, the command throws this exception,
673+ // but there's nothing we can do about it, nor can we know the number of scopes that
674+ // exist, and we shouldn't crash the debugger, so we just return no results instead.
675+ // All other exceptions should be thrown again.
671676 if ( ! ex . ErrorRecord . CategoryInfo . Reason . Equals ( "PSArgumentOutOfRangeException" ) )
672677 {
673678 throw ;
@@ -757,6 +762,8 @@ private bool AddToAutoVariables(PSObject psvariable, string scope)
757762 if ( ( ( variableScope & constantAllScope ) == constantAllScope )
758763 || ( ( variableScope & readonlyAllScope ) == readonlyAllScope ) )
759764 {
765+ // The constructor we are using here does not automatically add the dollar prefix,
766+ // so we do it manually.
760767 string prefixedVariableName = VariableDetails . DollarPrefix + variableName ;
761768 if ( globalScopeVariables . Children . ContainsKey ( prefixedVariableName ) )
762769 {
@@ -769,30 +776,27 @@ private bool AddToAutoVariables(PSObject psvariable, string scope)
769776
770777 private async Task FetchStackFramesAsync ( string scriptNameOverride )
771778 {
772- PSCommand psCommand = new PSCommand ( ) ;
773- // The serialization depth to retrieve variables from remote runspaces.
774- const int serializationDepth = 3 ;
775-
776779 // This glorious hack ensures that Get-PSCallStack returns a list of CallStackFrame
777780 // objects (or "deserialized" CallStackFrames) when attached to a runspace in another
778- // process. Without the intermediate variable Get-PSCallStack inexplicably returns
779- // an array of strings containing the formatted output of the CallStackFrame list.
780- string callStackVarName = $ "$global:{ PsesGlobalVariableNamePrefix } CallStack";
781-
782- string getPSCallStack = $ "Get-PSCallStack | ForEach-Object {{ [void]{ callStackVarName } .add(@($PSItem,$PSItem.GetFrameVariables())) }}";
783-
784- // If we're attached to a remote runspace, we need to serialize the callstack prior to transport
785- // because the default depth is too shallow
781+ // process. Without the intermediate variable Get-PSCallStack inexplicably returns an
782+ // array of strings containing the formatted output of the CallStackFrame list. So we
783+ // run a script that builds the list of CallStackFrames and their variables.
784+ const string callStackVarName = $ "$global:{ PsesGlobalVariableNamePrefix } CallStack";
785+ const string getPSCallStack = $ "Get-PSCallStack | ForEach-Object {{ [void]{ callStackVarName } .Add(@($PSItem, $PSItem.GetFrameVariables())) }}";
786+
787+ // If we're attached to a remote runspace, we need to serialize the list prior to
788+ // transport because the default depth is too shallow. From testing, we determined the
789+ // correct depth is 3. The script always calls `Get-PSCallStack`. On a local machine, we
790+ // just return its results. On a remote machine we serialize it first and then later
791+ // deserialize it.
786792 bool isOnRemoteMachine = _psesHost . CurrentRunspace . IsOnRemoteMachine ;
787793 string returnSerializedIfOnRemoteMachine = isOnRemoteMachine
788- ? $ "[Management.Automation.PSSerializer]::Serialize({ callStackVarName } , { serializationDepth } )"
794+ ? $ "[Management.Automation.PSSerializer]::Serialize({ callStackVarName } , 3 )"
789795 : callStackVarName ;
790796
791- // We have to deal with a shallow serialization depth with ExecutePSCommandAsync as well, hence the serializer to get full var information
792- psCommand . AddScript ( $ "[Collections.ArrayList]{ callStackVarName } = @(); { getPSCallStack } ; { returnSerializedIfOnRemoteMachine } ") ;
793-
794-
795- // PSObject is used here instead of the specific type because we get deserialized objects from remote sessions and want a common interface
797+ // PSObject is used here instead of the specific type because we get deserialized
798+ // objects from remote sessions and want a common interface.
799+ var psCommand = new PSCommand ( ) . AddScript ( $ "[Collections.ArrayList]{ callStackVarName } = @(); { getPSCallStack } ; { returnSerializedIfOnRemoteMachine } ") ;
796800 IReadOnlyList < PSObject > results = await _executionService . ExecutePSCommandAsync < PSObject > ( psCommand , CancellationToken . None ) . ConfigureAwait ( false ) ;
797801
798802 IEnumerable callStack = isOnRemoteMachine
@@ -816,11 +820,13 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
816820
817821 foreach ( DictionaryEntry entry in callStackVariables )
818822 {
819- // TODO: This should be deduplicated into a new function for the other variable handling as well
823+ // TODO: This should be deduplicated into a new function.
820824 object psVarValue = isOnRemoteMachine
821825 ? ( entry . Value as PSObject ) . Properties [ "Value" ] . Value
822826 : ( entry . Value as PSVariable ) . Value ;
823- // The constructor we are using here does not automatically add the dollar prefix
827+
828+ // The constructor we are using here does not automatically add the dollar
829+ // prefix, so we do it manually.
824830 string psVarName = VariableDetails . DollarPrefix + entry . Key . ToString ( ) ;
825831 var variableDetails = new VariableDetails ( psVarName , psVarValue ) { Id = nextVariableId ++ } ;
826832 variables . Add ( variableDetails ) ;
0 commit comments