1
- // Copyright (c) Microsoft Corporation.
1
+ // Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT License.
3
3
4
4
using System ;
17
17
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Execution ;
18
18
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Host ;
19
19
using Microsoft . PowerShell . EditorServices . Services . PowerShell . Debugging ;
20
+ using System . Collections ;
20
21
21
22
namespace Microsoft . PowerShell . EditorServices . Services
22
23
{
@@ -45,6 +46,7 @@ internal class DebugService
45
46
private List < VariableDetailsBase > variables ;
46
47
private VariableContainerDetails globalScopeVariables ;
47
48
private VariableContainerDetails scriptScopeVariables ;
49
+ private VariableContainerDetails localScopeVariables ;
48
50
private StackFrameDetails [ ] stackFrameDetails ;
49
51
private readonly PropertyInfo invocationTypeScriptPositionProperty ;
50
52
@@ -445,11 +447,6 @@ public async Task<string> SetVariableAsync(int variableContainerReferenceId, str
445
447
for ( int i = 0 ; i < stackFrames . Length ; i ++ )
446
448
{
447
449
var stackFrame = stackFrames [ i ] ;
448
- if ( stackFrame . LocalVariables . ContainsVariable ( variable . Id ) )
449
- {
450
- scope = i . ToString ( ) ;
451
- break ;
452
- }
453
450
}
454
451
}
455
452
@@ -626,13 +623,12 @@ internal async Task<StackFrameDetails[]> GetStackFramesAsync(CancellationToken c
626
623
public VariableScope [ ] GetVariableScopes ( int stackFrameId )
627
624
{
628
625
var stackFrames = this . GetStackFrames ( ) ;
629
- int localStackFrameVariableId = stackFrames [ stackFrameId ] . LocalVariables . Id ;
630
626
int autoVariablesId = stackFrames [ stackFrameId ] . AutoVariables . Id ;
631
627
632
628
return new VariableScope [ ]
633
629
{
634
630
new VariableScope ( autoVariablesId , VariableContainerDetails . AutoVariablesName ) ,
635
- new VariableScope ( localStackFrameVariableId , VariableContainerDetails . LocalScopeName ) ,
631
+ new VariableScope ( this . localScopeVariables . Id , VariableContainerDetails . LocalScopeName ) ,
636
632
new VariableScope ( this . scriptScopeVariables . Id , VariableContainerDetails . ScriptScopeName ) ,
637
633
new VariableScope ( this . globalScopeVariables . Id , VariableContainerDetails . GlobalScopeName ) ,
638
634
} ;
@@ -655,30 +651,27 @@ private async Task FetchStackFramesAndVariablesAsync(string scriptNameOverride)
655
651
new VariableDetails ( "Dummy" , null )
656
652
} ;
657
653
658
- // Must retrieve global/script variales before stack frame variables
659
- // as we check stack frame variables against globals.
660
- await FetchGlobalAndScriptVariablesAsync ( ) . ConfigureAwait ( false ) ;
654
+
655
+ // Must retrieve in order of broadest to narrowest scope for efficient deduplication: global, script, local
656
+ this . globalScopeVariables =
657
+ await FetchVariableContainerAsync ( VariableContainerDetails . GlobalScopeName ) . ConfigureAwait ( false ) ;
658
+
659
+ this . scriptScopeVariables =
660
+ await FetchVariableContainerAsync ( VariableContainerDetails . ScriptScopeName ) . ConfigureAwait ( false ) ;
661
+
662
+ this . localScopeVariables =
663
+ await FetchVariableContainerAsync ( VariableContainerDetails . LocalScopeName ) . ConfigureAwait ( false ) ;
664
+
661
665
await FetchStackFramesAsync ( scriptNameOverride ) . ConfigureAwait ( false ) ;
666
+
662
667
}
663
668
finally
664
669
{
665
670
this . debugInfoHandle . Release ( ) ;
666
671
}
667
672
}
668
673
669
- private async Task FetchGlobalAndScriptVariablesAsync ( )
670
- {
671
- // Retrieve globals first as script variable retrieval needs to search globals.
672
- this . globalScopeVariables =
673
- await FetchVariableContainerAsync ( VariableContainerDetails . GlobalScopeName , null ) . ConfigureAwait ( false ) ;
674
-
675
- this . scriptScopeVariables =
676
- await FetchVariableContainerAsync ( VariableContainerDetails . ScriptScopeName , null ) . ConfigureAwait ( false ) ;
677
- }
678
-
679
- private async Task < VariableContainerDetails > FetchVariableContainerAsync (
680
- string scope ,
681
- VariableContainerDetails autoVariables )
674
+ private async Task < VariableContainerDetails > FetchVariableContainerAsync ( string scope )
682
675
{
683
676
PSCommand psCommand = new PSCommand ( )
684
677
. AddCommand ( "Get-Variable" )
@@ -704,11 +697,6 @@ private async Task<VariableContainerDetails> FetchVariableContainerAsync(
704
697
var variableDetails = new VariableDetails ( psVariableObject ) { Id = this . nextVariableId ++ } ;
705
698
this . variables . Add ( variableDetails ) ;
706
699
scopeVariableContainer . Children . Add ( variableDetails . Name , variableDetails ) ;
707
-
708
- if ( ( autoVariables != null ) && AddToAutoVariables ( psVariableObject , scope ) )
709
- {
710
- autoVariables . Children . Add ( variableDetails . Name , variableDetails ) ;
711
- }
712
700
}
713
701
}
714
702
@@ -792,55 +780,90 @@ private bool AddToAutoVariables(PSObject psvariable, string scope)
792
780
private async Task FetchStackFramesAsync ( string scriptNameOverride )
793
781
{
794
782
PSCommand psCommand = new PSCommand ( ) ;
783
+ // The serialization depth to retrieve variables from remote runspaces.
784
+ const int serializationDepth = 3 ;
795
785
796
786
// This glorious hack ensures that Get-PSCallStack returns a list of CallStackFrame
797
787
// objects (or "deserialized" CallStackFrames) when attached to a runspace in another
798
788
// process. Without the intermediate variable Get-PSCallStack inexplicably returns
799
789
// an array of strings containing the formatted output of the CallStackFrame list.
800
- var callStackVarName = $ "$global:{ PsesGlobalVariableNamePrefix } CallStack";
801
- psCommand . AddScript ( $ "{ callStackVarName } = Get-PSCallStack; { callStackVarName } ") ;
790
+ string callStackVarName = $ "$global:{ PsesGlobalVariableNamePrefix } CallStack";
791
+
792
+ string getPSCallStack = $ "Get-PSCallStack | ForEach-Object {{ [void]{ callStackVarName } .add(@($PSItem,$PSItem.GetFrameVariables())) }}";
793
+
794
+ // If we're attached to a remote runspace, we need to serialize the callstack prior to transport
795
+ // because the default depth is too shallow
796
+ bool isOnRemoteMachine = _psesHost . CurrentRunspace . IsOnRemoteMachine ;
797
+ string returnSerializedIfOnRemoteMachine = isOnRemoteMachine
798
+ ? $ "[Management.Automation.PSSerializer]::Serialize({ callStackVarName } , { serializationDepth } )"
799
+ : callStackVarName ;
802
800
803
- var results = await _executionService . ExecutePSCommandAsync < PSObject > ( psCommand , CancellationToken . None ) . ConfigureAwait ( false ) ;
801
+ // We have to deal with a shallow serialization depth with ExecutePSCommandAsync as well, hence the serializer to get full var information
802
+ psCommand . AddScript ( $ "[Collections.ArrayList]{ callStackVarName } = @(); { getPSCallStack } ; { returnSerializedIfOnRemoteMachine } ") ;
804
803
805
- var callStackFrames = results . ToArray ( ) ;
806
804
807
- this . stackFrameDetails = new StackFrameDetails [ callStackFrames . Length ] ;
805
+ // PSObject is used here instead of the specific type because we get deserialized objects from remote sessions and want a common interface
806
+ IReadOnlyList < PSObject > results = await _executionService . ExecutePSCommandAsync < PSObject > ( psCommand , CancellationToken . None ) . ConfigureAwait ( false ) ;
808
807
809
- for ( int i = 0 ; i < callStackFrames . Length ; i ++ )
808
+ IEnumerable callStack = isOnRemoteMachine
809
+ ? ( PSSerializer . Deserialize ( results [ 0 ] . BaseObject as string ) as PSObject ) . BaseObject as IList
810
+ : results ;
811
+
812
+ List < StackFrameDetails > stackFrameDetailList = new List < StackFrameDetails > ( ) ;
813
+ foreach ( var callStackFrameItem in callStack )
810
814
{
811
- VariableContainerDetails autoVariables =
812
- new VariableContainerDetails (
813
- this . nextVariableId ++ ,
814
- VariableContainerDetails . AutoVariablesName ) ;
815
+ var callStackFrameComponents = ( callStackFrameItem as PSObject ) . BaseObject as IList ;
816
+ var callStackFrame = callStackFrameComponents [ 0 ] as PSObject ;
817
+ IDictionary callStackVariables = isOnRemoteMachine
818
+ ? ( callStackFrameComponents [ 1 ] as PSObject ) . BaseObject as IDictionary
819
+ : callStackFrameComponents [ 1 ] as IDictionary ;
815
820
816
- this . variables . Add ( autoVariables ) ;
821
+ var autoVariables = new VariableContainerDetails (
822
+ nextVariableId ++ ,
823
+ VariableContainerDetails . AutoVariablesName ) ;
817
824
818
- VariableContainerDetails localVariables =
819
- await FetchVariableContainerAsync ( i . ToString ( ) , autoVariables ) . ConfigureAwait ( false ) ;
825
+ variables . Add ( autoVariables ) ;
820
826
821
- // When debugging, this is the best way I can find to get what is likely the workspace root.
822
- // This is controlled by the "cwd:" setting in the launch config.
823
- string workspaceRootPath = _psesHost . InitialWorkingDirectory ;
827
+ foreach ( DictionaryEntry entry in callStackVariables )
828
+ {
829
+ // TODO: This should be deduplicated into a new function for the other variable handling as well
830
+ object psVarValue = isOnRemoteMachine
831
+ ? ( entry . Value as PSObject ) . Properties [ "Value" ] . Value
832
+ : ( entry . Value as PSVariable ) . Value ;
833
+ // The constructor we are using here does not automatically add the dollar prefix
834
+ string psVarName = VariableDetails . DollarPrefix + entry . Key . ToString ( ) ;
835
+ var variableDetails = new VariableDetails ( psVarName , psVarValue ) { Id = nextVariableId ++ } ;
836
+ variables . Add ( variableDetails ) ;
837
+
838
+ if ( AddToAutoVariables ( new PSObject ( entry . Value ) , scope : null ) )
839
+ {
840
+ autoVariables . Children . Add ( variableDetails . Name , variableDetails ) ;
841
+ }
842
+ }
824
843
825
- this . stackFrameDetails [ i ] =
826
- StackFrameDetails . Create ( callStackFrames [ i ] , autoVariables , localVariables , workspaceRootPath ) ;
844
+ var stackFrameDetailsEntry = StackFrameDetails . Create ( callStackFrame , autoVariables ) ;
827
845
828
- string stackFrameScriptPath = this . stackFrameDetails [ i ] . ScriptPath ;
829
- if ( scriptNameOverride != null &&
846
+ string stackFrameScriptPath = stackFrameDetailsEntry . ScriptPath ;
847
+ if ( scriptNameOverride is not null &&
830
848
string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
831
849
{
832
- this . stackFrameDetails [ i ] . ScriptPath = scriptNameOverride ;
850
+ stackFrameDetailsEntry . ScriptPath = scriptNameOverride ;
833
851
}
834
- else if ( _psesHost . CurrentRunspace . IsOnRemoteMachine
835
- && this . remoteFileManager != null
852
+ else if ( isOnRemoteMachine
853
+ && remoteFileManager is not null
836
854
&& ! string . Equals ( stackFrameScriptPath , StackFrameDetails . NoFileScriptPath ) )
837
855
{
838
- this . stackFrameDetails [ i ] . ScriptPath =
839
- this . remoteFileManager . GetMappedPath (
856
+ stackFrameDetailsEntry . ScriptPath =
857
+ remoteFileManager . GetMappedPath (
840
858
stackFrameScriptPath ,
841
859
_psesHost . CurrentRunspace ) ;
842
860
}
861
+
862
+ stackFrameDetailList . Add (
863
+ stackFrameDetailsEntry ) ;
843
864
}
865
+
866
+ stackFrameDetails = stackFrameDetailList . ToArray ( ) ;
844
867
}
845
868
846
869
private static string TrimScriptListingLine ( PSObject scriptLineObj , ref int prefixLength )
0 commit comments