1
- // Copyright (c) Microsoft Corporation.
1
+ // Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT License.
3
3
4
4
using System ;
@@ -68,7 +68,7 @@ public VariableDetails(PSObject psVariableObject)
68
68
/// The PSPropertyInfo instance from which variable details will be obtained.
69
69
/// </param>
70
70
public VariableDetails ( PSPropertyInfo psProperty )
71
- : this ( psProperty . Name , psProperty . Value )
71
+ : this ( psProperty . Name , SafeGetValue ( psProperty ) )
72
72
{
73
73
}
74
74
@@ -98,16 +98,11 @@ public VariableDetails(string name, object value)
98
98
/// If this variable instance is expandable, this method returns the
99
99
/// details of its children. Otherwise it returns an empty array.
100
100
/// </summary>
101
- /// <returns></returns>
102
101
public override VariableDetailsBase [ ] GetChildren ( ILogger logger )
103
102
{
104
103
if ( IsExpandable )
105
104
{
106
- if ( cachedChildren == null )
107
- {
108
- cachedChildren = GetChildren ( ValueObject , logger ) ;
109
- }
110
-
105
+ cachedChildren ??= GetChildren ( ValueObject , logger ) ;
111
106
return cachedChildren ;
112
107
}
113
108
@@ -118,6 +113,20 @@ public override VariableDetailsBase[] GetChildren(ILogger logger)
118
113
119
114
#region Private Methods
120
115
116
+ private static object SafeGetValue ( PSPropertyInfo psProperty )
117
+ {
118
+ try
119
+ {
120
+ return psProperty . Value ;
121
+ }
122
+ catch ( GetValueInvocationException ex )
123
+ {
124
+ // Sometimes we can't get the value, like ExitCode, for reasons beyond our control,
125
+ // so just return the message from the exception that arises.
126
+ return new UnableToRetrievePropertyMessage { Name = psProperty . Name , Message = ex . Message } ;
127
+ }
128
+ }
129
+
121
130
private static bool GetIsExpandable ( object valueObject )
122
131
{
123
132
if ( valueObject == null )
@@ -131,9 +140,7 @@ private static bool GetIsExpandable(object valueObject)
131
140
valueObject = psobject . BaseObject ;
132
141
}
133
142
134
- Type valueType =
135
- valueObject ? . GetType ( ) ;
136
-
143
+ Type valueType = valueObject ? . GetType ( ) ;
137
144
TypeInfo valueTypeInfo = valueType . GetTypeInfo ( ) ;
138
145
139
146
return
@@ -236,7 +243,7 @@ private static string InsertDimensionSize(string value, int dimensionSize)
236
243
return value + ": " + dimensionSize ;
237
244
}
238
245
239
- private VariableDetails [ ] GetChildren ( object obj , ILogger logger )
246
+ private static VariableDetails [ ] GetChildren ( object obj , ILogger logger )
240
247
{
241
248
List < VariableDetails > childVariables = new ( ) ;
242
249
@@ -245,86 +252,82 @@ private VariableDetails[] GetChildren(object obj, ILogger logger)
245
252
return childVariables . ToArray ( ) ;
246
253
}
247
254
248
- try
249
- {
250
- PSObject psObject = obj as PSObject ;
255
+ // NOTE: Variable expansion now takes place on the pipeline thread as an async delegate,
256
+ // so expansion of children that cause PowerShell script code to execute should
257
+ // generally work. However, we might need more error handling.
258
+ PSObject psObject = obj as PSObject ;
251
259
252
- if ( ( psObject != null ) &&
253
- ( psObject . TypeNames [ 0 ] == typeof ( PSCustomObject ) . ToString ( ) ) )
260
+ if ( ( psObject != null ) &&
261
+ ( psObject . TypeNames [ 0 ] == typeof ( PSCustomObject ) . ToString ( ) ) )
262
+ {
263
+ // PowerShell PSCustomObject's properties are completely defined by the ETS type system.
264
+ logger . LogDebug ( "PSObject was a PSCustomObject" ) ;
265
+ childVariables . AddRange (
266
+ psObject
267
+ . Properties
268
+ . Select ( p => new VariableDetails ( p ) ) ) ;
269
+ }
270
+ else
271
+ {
272
+ // If a PSObject other than a PSCustomObject, unwrap it.
273
+ if ( psObject != null )
254
274
{
255
- // PowerShell PSCustomObject's properties are completely defined by the ETS type system.
275
+ // First add the PSObject's ETS properties
276
+ logger . LogDebug ( "PSObject was something else, first getting ETS properties" ) ;
256
277
childVariables . AddRange (
257
278
psObject
258
279
. Properties
280
+ // Here we check the object's MemberType against the `Properties`
281
+ // bit-mask to determine if this is a property. Hence the selection
282
+ // will only include properties.
283
+ . Where ( p => ( PSMemberTypes . Properties & p . MemberType ) is not 0 )
259
284
. Select ( p => new VariableDetails ( p ) ) ) ;
285
+
286
+ obj = psObject . BaseObject ;
260
287
}
261
- else
262
- {
263
- // If a PSObject other than a PSCustomObject, unwrap it.
264
- if ( psObject != null )
265
- {
266
- // First add the PSObject's ETS properties
267
- childVariables . AddRange (
268
- psObject
269
- . Properties
270
- // Here we check the object's MemberType against the `Properties`
271
- // bit-mask to determine if this is a property. Hence the selection
272
- // will only include properties.
273
- . Where ( p => ( PSMemberTypes . Properties & p . MemberType ) is not 0 )
274
- . Select ( p => new VariableDetails ( p ) ) ) ;
275
-
276
- obj = psObject . BaseObject ;
277
- }
278
288
279
- // We're in the realm of regular, unwrapped .NET objects
280
- if ( obj is IDictionary dictionary )
289
+ // We're in the realm of regular, unwrapped .NET objects
290
+ if ( obj is IDictionary dictionary )
291
+ {
292
+ logger . LogDebug ( "PSObject was an IDictionary" ) ;
293
+ // Buckle up kids, this is a bit weird. We could not use the LINQ
294
+ // operator OfType<DictionaryEntry>. Even though R# will squiggle the
295
+ // "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT!
296
+ // The reason is that LINQ extension methods work with objects of type
297
+ // IEnumerable. Objects of type Dictionary<,>, respond to iteration via
298
+ // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic
299
+ // dictionaries like HashTable return DictionaryEntry objects.
300
+ // It turns out that iteration via C#'s foreach loop, operates on the variable's
301
+ // type which in this case is IDictionary. IDictionary was designed to always
302
+ // return DictionaryEntry objects upon iteration and the Dictionary<,> implementation
303
+ // honors that when the object is reinterpreted as an IDictionary object.
304
+ // FYI, a test case for this is to open $PSBoundParameters when debugging a
305
+ // function that defines parameters and has been passed parameters.
306
+ // If you open the $PSBoundParameters variable node in this scenario and see nothing,
307
+ // this code is broken.
308
+ foreach ( DictionaryEntry entry in dictionary )
281
309
{
282
- // Buckle up kids, this is a bit weird. We could not use the LINQ
283
- // operator OfType<DictionaryEntry>. Even though R# will squiggle the
284
- // "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT!
285
- // The reason is that LINQ extension methods work with objects of type
286
- // IEnumerable. Objects of type Dictionary<,>, respond to iteration via
287
- // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic
288
- // dictionaries like HashTable return DictionaryEntry objects.
289
- // It turns out that iteration via C#'s foreach loop, operates on the variable's
290
- // type which in this case is IDictionary. IDictionary was designed to always
291
- // return DictionaryEntry objects upon iteration and the Dictionary<,> implementation
292
- // honors that when the object is reinterpreted as an IDictionary object.
293
- // FYI, a test case for this is to open $PSBoundParameters when debugging a
294
- // function that defines parameters and has been passed parameters.
295
- // If you open the $PSBoundParameters variable node in this scenario and see nothing,
296
- // this code is broken.
297
- foreach ( DictionaryEntry entry in dictionary )
298
- {
299
- childVariables . Add (
300
- new VariableDetails (
301
- "[" + entry . Key + "]" ,
302
- entry ) ) ;
303
- }
310
+ childVariables . Add (
311
+ new VariableDetails (
312
+ "[" + entry . Key + "]" ,
313
+ entry ) ) ;
304
314
}
305
- else if ( obj is IEnumerable enumerable and not string )
315
+ }
316
+ else if ( obj is IEnumerable enumerable and not string )
317
+ {
318
+ logger . LogDebug ( "PSObject was an IEnumerable" ) ;
319
+ int i = 0 ;
320
+ foreach ( object item in enumerable )
306
321
{
307
- int i = 0 ;
308
- foreach ( object item in enumerable )
309
- {
310
- childVariables . Add (
311
- new VariableDetails (
312
- "[" + i ++ + "]" ,
313
- item ) ) ;
314
- }
322
+ childVariables . Add (
323
+ new VariableDetails (
324
+ "[" + i ++ + "]" ,
325
+ item ) ) ;
315
326
}
316
-
317
- AddDotNetProperties ( obj , childVariables ) ;
318
327
}
319
- }
320
- catch ( GetValueInvocationException ex )
321
- {
322
- // This exception occurs when accessing the value of a
323
- // variable causes a script to be executed. Right now
324
- // we aren't loading children on the pipeline thread so
325
- // this causes an exception to be raised. In this case,
326
- // just return an empty list of children.
327
- logger . LogWarning ( $ "Failed to get properties of variable { Name } , value invocation was attempted: { ex . Message } ") ;
328
+
329
+ logger . LogDebug ( "Adding .NET properties to PSObject" ) ;
330
+ AddDotNetProperties ( obj , childVariables ) ;
328
331
}
329
332
330
333
return childVariables . ToArray ( ) ;
@@ -342,9 +345,8 @@ protected static void AddDotNetProperties(object obj, List<VariableDetails> chil
342
345
return ;
343
346
}
344
347
345
- PropertyInfo [ ] properties = objectType . GetProperties ( BindingFlags . Public | BindingFlags . Instance ) ;
346
-
347
- foreach ( PropertyInfo property in properties )
348
+ // Search all the public instance properties and add those missing.
349
+ foreach ( PropertyInfo property in objectType . GetProperties ( BindingFlags . Public | BindingFlags . Instance ) )
348
350
{
349
351
// Don't display indexer properties, it causes an exception anyway.
350
352
if ( property . GetIndexParameters ( ) . Length > 0 )
@@ -354,10 +356,11 @@ protected static void AddDotNetProperties(object obj, List<VariableDetails> chil
354
356
355
357
try
356
358
{
357
- childVariables . Add (
358
- new VariableDetails (
359
- property . Name ,
360
- property . GetValue ( obj ) ) ) ;
359
+ // Only add unique properties because we may have already added some.
360
+ if ( ! childVariables . Exists ( p => p . Name == property . Name ) )
361
+ {
362
+ childVariables . Add ( new VariableDetails ( property . Name , property . GetValue ( obj ) ) ) ;
363
+ }
361
364
}
362
365
catch ( Exception ex )
363
366
{
@@ -371,21 +374,19 @@ protected static void AddDotNetProperties(object obj, List<VariableDetails> chil
371
374
childVariables . Add (
372
375
new VariableDetails (
373
376
property . Name ,
374
- new UnableToRetrievePropertyMessage (
375
- "Error retrieving property - " + ex . GetType ( ) . Name ) ) ) ;
377
+ new UnableToRetrievePropertyMessage { Name = property . Name , Message = ex . Message } ) ) ;
376
378
}
377
379
}
378
380
}
379
381
380
382
#endregion
381
383
382
- private struct UnableToRetrievePropertyMessage
384
+ private record UnableToRetrievePropertyMessage
383
385
{
384
- public UnableToRetrievePropertyMessage ( string message ) => Message = message ;
385
-
386
- public string Message { get ; }
386
+ public string Name { get ; init ; }
387
+ public string Message { get ; init ; }
387
388
388
- public override string ToString ( ) => "<" + Message + "> ";
389
+ public override string ToString ( ) => $ "Error retrieving property '$ { Name } ': $ { Message } ";
389
390
}
390
391
}
391
392
0 commit comments