@@ -229,7 +229,7 @@ private static string InsertDimensionSize(string value, int dimensionSize)
229229 return value + ": " + dimensionSize ;
230230 }
231231
232- private VariableDetails [ ] GetChildren ( object obj , ILogger logger )
232+ private static VariableDetails [ ] GetChildren ( object obj , ILogger logger )
233233 {
234234 List < VariableDetails > childVariables = new ( ) ;
235235
@@ -238,86 +238,82 @@ private VariableDetails[] GetChildren(object obj, ILogger logger)
238238 return childVariables . ToArray ( ) ;
239239 }
240240
241- try
242- {
243- PSObject psObject = obj as PSObject ;
241+ // NOTE: Variable expansion now takes place on the pipeline thread as an async delegate,
242+ // so expansion of children that cause PowerShell script code to execute should
243+ // generally work. However, we might need more error handling.
244+ PSObject psObject = obj as PSObject ;
244245
245- if ( ( psObject != null ) &&
246- ( psObject . TypeNames [ 0 ] == typeof ( PSCustomObject ) . ToString ( ) ) )
246+ if ( ( psObject != null ) &&
247+ ( psObject . TypeNames [ 0 ] == typeof ( PSCustomObject ) . ToString ( ) ) )
248+ {
249+ // PowerShell PSCustomObject's properties are completely defined by the ETS type system.
250+ logger . LogDebug ( "PSObject was a PSCustomObject" ) ;
251+ childVariables . AddRange (
252+ psObject
253+ . Properties
254+ . Select ( p => new VariableDetails ( p ) ) ) ;
255+ }
256+ else
257+ {
258+ // If a PSObject other than a PSCustomObject, unwrap it.
259+ if ( psObject != null )
247260 {
248- // PowerShell PSCustomObject's properties are completely defined by the ETS type system.
261+ // First add the PSObject's ETS properties
262+ logger . LogDebug ( "PSObject was something else, first getting ETS properties" ) ;
249263 childVariables . AddRange (
250264 psObject
251265 . Properties
266+ // Here we check the object's MemberType against the `Properties`
267+ // bit-mask to determine if this is a property. Hence the selection
268+ // will only include properties.
269+ . Where ( p => ( PSMemberTypes . Properties & p . MemberType ) is not 0 )
252270 . Select ( p => new VariableDetails ( p ) ) ) ;
271+
272+ obj = psObject . BaseObject ;
253273 }
254- else
274+
275+ // We're in the realm of regular, unwrapped .NET objects
276+ if ( obj is IDictionary dictionary )
255277 {
256- // If a PSObject other than a PSCustomObject, unwrap it.
257- if ( psObject != null )
278+ logger . LogDebug ( "PSObject was an IDictionary" ) ;
279+ // Buckle up kids, this is a bit weird. We could not use the LINQ
280+ // operator OfType<DictionaryEntry>. Even though R# will squiggle the
281+ // "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT!
282+ // The reason is that LINQ extension methods work with objects of type
283+ // IEnumerable. Objects of type Dictionary<,>, respond to iteration via
284+ // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic
285+ // dictionaries like HashTable return DictionaryEntry objects.
286+ // It turns out that iteration via C#'s foreach loop, operates on the variable's
287+ // type which in this case is IDictionary. IDictionary was designed to always
288+ // return DictionaryEntry objects upon iteration and the Dictionary<,> implementation
289+ // honors that when the object is reinterpreted as an IDictionary object.
290+ // FYI, a test case for this is to open $PSBoundParameters when debugging a
291+ // function that defines parameters and has been passed parameters.
292+ // If you open the $PSBoundParameters variable node in this scenario and see nothing,
293+ // this code is broken.
294+ foreach ( DictionaryEntry entry in dictionary )
258295 {
259- // First add the PSObject's ETS properties
260- childVariables . AddRange (
261- psObject
262- . Properties
263- // Here we check the object's MemberType against the `Properties`
264- // bit-mask to determine if this is a property. Hence the selection
265- // will only include properties.
266- . Where ( p => ( PSMemberTypes . Properties & p . MemberType ) is not 0 )
267- . Select ( p => new VariableDetails ( p ) ) ) ;
268-
269- obj = psObject . BaseObject ;
296+ childVariables . Add (
297+ new VariableDetails (
298+ "[" + entry . Key + "]" ,
299+ entry ) ) ;
270300 }
271-
272- // We're in the realm of regular, unwrapped .NET objects
273- if ( obj is IDictionary dictionary )
274- {
275- // Buckle up kids, this is a bit weird. We could not use the LINQ
276- // operator OfType<DictionaryEntry>. Even though R# will squiggle the
277- // "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT!
278- // The reason is that LINQ extension methods work with objects of type
279- // IEnumerable. Objects of type Dictionary<,>, respond to iteration via
280- // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic
281- // dictionaries like HashTable return DictionaryEntry objects.
282- // It turns out that iteration via C#'s foreach loop, operates on the variable's
283- // type which in this case is IDictionary. IDictionary was designed to always
284- // return DictionaryEntry objects upon iteration and the Dictionary<,> implementation
285- // honors that when the object is reinterpreted as an IDictionary object.
286- // FYI, a test case for this is to open $PSBoundParameters when debugging a
287- // function that defines parameters and has been passed parameters.
288- // If you open the $PSBoundParameters variable node in this scenario and see nothing,
289- // this code is broken.
290- foreach ( DictionaryEntry entry in dictionary )
291- {
292- childVariables . Add (
293- new VariableDetails (
294- "[" + entry . Key + "]" ,
295- entry ) ) ;
296- }
297- }
298- else if ( obj is IEnumerable enumerable and not string )
301+ }
302+ else if ( obj is IEnumerable enumerable and not string )
303+ {
304+ logger . LogDebug ( "PSObject was an IEnumerable" ) ;
305+ int i = 0 ;
306+ foreach ( object item in enumerable )
299307 {
300- int i = 0 ;
301- foreach ( object item in enumerable )
302- {
303- childVariables . Add (
304- new VariableDetails (
305- "[" + i ++ + "]" ,
306- item ) ) ;
307- }
308+ childVariables . Add (
309+ new VariableDetails (
310+ "[" + i ++ + "]" ,
311+ item ) ) ;
308312 }
309-
310- AddDotNetProperties ( obj , childVariables ) ;
311313 }
312- }
313- catch ( GetValueInvocationException ex )
314- {
315- // This exception occurs when accessing the value of a
316- // variable causes a script to be executed. Right now
317- // we aren't loading children on the pipeline thread so
318- // this causes an exception to be raised. In this case,
319- // just return an empty list of children.
320- logger . LogWarning ( $ "Failed to get properties of variable { Name } , value invocation was attempted: { ex . Message } ") ;
314+
315+ logger . LogDebug ( "Adding .NET properties to PSObject" ) ;
316+ AddDotNetProperties ( obj , childVariables ) ;
321317 }
322318
323319 return childVariables . ToArray ( ) ;
0 commit comments