@@ -668,6 +668,11 @@ private async Task<VariableContainerDetails> FetchVariableContainerAsync(string
668
668
}
669
669
catch ( CmdletInvocationException ex )
670
670
{
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.
671
676
if ( ! ex . ErrorRecord . CategoryInfo . Reason . Equals ( "PSArgumentOutOfRangeException" ) )
672
677
{
673
678
throw ;
@@ -757,6 +762,8 @@ private bool AddToAutoVariables(PSObject psvariable, string scope)
757
762
if ( ( ( variableScope & constantAllScope ) == constantAllScope )
758
763
|| ( ( variableScope & readonlyAllScope ) == readonlyAllScope ) )
759
764
{
765
+ // The constructor we are using here does not automatically add the dollar prefix,
766
+ // so we do it manually.
760
767
string prefixedVariableName = VariableDetails . DollarPrefix + variableName ;
761
768
if ( globalScopeVariables . Children . ContainsKey ( prefixedVariableName ) )
762
769
{
@@ -769,30 +776,27 @@ private bool AddToAutoVariables(PSObject psvariable, string scope)
769
776
770
777
private async Task FetchStackFramesAsync ( string scriptNameOverride )
771
778
{
772
- PSCommand psCommand = new PSCommand ( ) ;
773
- // The serialization depth to retrieve variables from remote runspaces.
774
- const int serializationDepth = 3 ;
775
-
776
779
// This glorious hack ensures that Get-PSCallStack returns a list of CallStackFrame
777
780
// 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.
786
792
bool isOnRemoteMachine = _psesHost . CurrentRunspace . IsOnRemoteMachine ;
787
793
string returnSerializedIfOnRemoteMachine = isOnRemoteMachine
788
- ? $ "[Management.Automation.PSSerializer]::Serialize({ callStackVarName } , { serializationDepth } )"
794
+ ? $ "[Management.Automation.PSSerializer]::Serialize({ callStackVarName } , 3 )"
789
795
: callStackVarName ;
790
796
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 } ") ;
796
800
IReadOnlyList < PSObject > results = await _executionService . ExecutePSCommandAsync < PSObject > ( psCommand , CancellationToken . None ) . ConfigureAwait ( false ) ;
797
801
798
802
IEnumerable callStack = isOnRemoteMachine
@@ -816,11 +820,13 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
816
820
817
821
foreach ( DictionaryEntry entry in callStackVariables )
818
822
{
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.
820
824
object psVarValue = isOnRemoteMachine
821
825
? ( entry . Value as PSObject ) . Properties [ "Value" ] . Value
822
826
: ( 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.
824
830
string psVarName = VariableDetails . DollarPrefix + entry . Key . ToString ( ) ;
825
831
var variableDetails = new VariableDetails ( psVarName , psVarValue ) { Id = nextVariableId ++ } ;
826
832
variables . Add ( variableDetails ) ;
0 commit comments