@@ -129,7 +129,8 @@ private static bool GetIsExpandable(object valueObject)
129
129
130
130
return
131
131
valueObject != null &&
132
- ! valueType . IsValueType &&
132
+ ! valueType . IsPrimitive &&
133
+ ! ( valueObject is UnableToRetrievePropertyMessage ) &&
133
134
! ( valueObject is string ) ; // Strings get treated as IEnumerables
134
135
}
135
136
@@ -143,13 +144,23 @@ private static string GetValueString(object value, bool isExpandable)
143
144
}
144
145
else if ( isExpandable )
145
146
{
146
- Type objType = value . GetType ( ) ;
147
+ Type objType = value . GetType ( ) ;
147
148
148
- // Get the "value" for an expandable object. This will either
149
- // be the short type name or the ToString() response if ToString()
150
- // responds with something other than the type name.
151
- if ( value . ToString ( ) . Equals ( objType . FullName ) )
149
+ // Get the "value" for an expandable object.
150
+ if ( value is DictionaryEntry )
152
151
{
152
+ // For DictionaryEntry - display the key/value as the value.
153
+ var entry = ( DictionaryEntry ) value ;
154
+ valueString =
155
+ string . Format (
156
+ "[{0}, {1}]" ,
157
+ entry . Key ,
158
+ GetValueString ( entry . Value , GetIsExpandable ( entry . Value ) ) ) ;
159
+ }
160
+ else if ( value . ToString ( ) . Equals ( objType . ToString ( ) ) )
161
+ {
162
+ // If the ToString() matches the type name, then display the type
163
+ // name in PowerShell format.
153
164
string shortTypeName = objType . Name ;
154
165
155
166
// For arrays and ICollection, display the number of contained items.
@@ -176,7 +187,8 @@ private static string GetValueString(object value, bool isExpandable)
176
187
}
177
188
else
178
189
{
179
- if ( value . GetType ( ) == typeof ( string ) )
190
+ // ToString() output is not the typename, so display that as this object's value
191
+ if ( value is string )
180
192
{
181
193
valueString = "\" " + value + "\" " ;
182
194
}
@@ -215,6 +227,11 @@ private static VariableDetails[] GetChildren(object obj)
215
227
{
216
228
List < VariableDetails > childVariables = new List < VariableDetails > ( ) ;
217
229
230
+ if ( obj == null )
231
+ {
232
+ return childVariables . ToArray ( ) ;
233
+ }
234
+
218
235
PSObject psObject = obj as PSObject ;
219
236
IDictionary dictionary = obj as IDictionary ;
220
237
IEnumerable enumerable = obj as IEnumerable ;
@@ -223,59 +240,55 @@ private static VariableDetails[] GetChildren(object obj)
223
240
{
224
241
if ( psObject != null )
225
242
{
243
+ // PowerShell wrapped objects can have extra ETS properties so let's use
244
+ // PowerShell's infrastructure to get those properties.
226
245
childVariables . AddRange (
227
246
psObject
228
247
. Properties
229
248
. Select ( p => new VariableDetails ( p ) ) ) ;
230
249
}
231
- else if ( dictionary != null )
250
+ else
232
251
{
233
- childVariables . AddRange (
234
- dictionary
235
- . OfType < DictionaryEntry > ( )
236
- . Select ( e => new VariableDetails ( e . Key . ToString ( ) , e . Value ) ) ) ;
237
- }
238
- else if ( enumerable != null && ! ( obj is string ) )
239
- {
240
- int i = 0 ;
241
- foreach ( var item in enumerable )
242
- {
243
- childVariables . Add (
244
- new VariableDetails (
245
- string . Format ( "[{0}]" , i ) ,
246
- item ) ) ;
247
-
248
- i ++ ;
249
- }
250
- }
251
- else if ( obj != null )
252
- {
253
- // Object must be a normal .NET type, pull all of its
254
- // properties and their values
255
- Type objectType = obj . GetType ( ) ;
256
- var properties =
257
- objectType . GetProperties (
258
- BindingFlags . Public | BindingFlags . Instance ) ;
259
-
260
- foreach ( var property in properties )
261
- {
262
- try
252
+ // We're in the realm of regular, unwrapped .NET objects
253
+ if ( dictionary != null )
254
+ {
255
+ // Buckle up kids, this is a bit weird. We could not use the LINQ
256
+ // operator OfType<DictionaryEntry>. Even though R# will squiggle the
257
+ // "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT!
258
+ // The reason is that LINQ extension methods work with objects of type
259
+ // IEnumerable. Objects of type Dictionary<,>, respond to iteration via
260
+ // IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic
261
+ // dictionaries like HashTable return DictionaryEntry objects.
262
+ // It turns out that iteration via C#'s foreach loop, operates on the variable's
263
+ // type which in this case is IDictionary. IDictionary was designed to always
264
+ // return DictionaryEntry objects upon iteration and the Dictionary<,> implementation
265
+ // honors that when the object is reintepreted as an IDictionary object.
266
+ // FYI, a test case for this is to open $PSBoundParameters when debugging a
267
+ // function that defines parameters and has been passed parameters.
268
+ // If you open the $PSBoundParameters variable node in this scenario and see nothing,
269
+ // this code is broken.
270
+ int i = 0 ;
271
+ foreach ( DictionaryEntry entry in dictionary )
263
272
{
264
273
childVariables . Add (
265
274
new VariableDetails (
266
- property . Name ,
267
- property . GetValue ( obj ) ) ) ;
275
+ "[" + i ++ + "]" ,
276
+ entry ) ) ;
268
277
}
269
- catch ( Exception )
278
+ }
279
+ else if ( enumerable != null && ! ( obj is string ) )
280
+ {
281
+ int i = 0 ;
282
+ foreach ( var item in enumerable )
270
283
{
271
- // Some properties can throw exceptions, add the property
272
- // name and empty string
273
284
childVariables . Add (
274
285
new VariableDetails (
275
- property . Name ,
276
- string . Empty ) ) ;
286
+ "[" + i ++ + "]" ,
287
+ item ) ) ;
277
288
}
278
289
}
290
+
291
+ AddDotNetProperties ( obj , childVariables ) ;
279
292
}
280
293
}
281
294
catch ( GetValueInvocationException )
@@ -290,6 +303,61 @@ private static VariableDetails[] GetChildren(object obj)
290
303
return childVariables . ToArray ( ) ;
291
304
}
292
305
306
+ private static void AddDotNetProperties ( object obj , List < VariableDetails > childVariables )
307
+ {
308
+ Type objectType = obj . GetType ( ) ;
309
+ var properties =
310
+ objectType . GetProperties (
311
+ BindingFlags . Public | BindingFlags . Instance ) ;
312
+
313
+ foreach ( var property in properties )
314
+ {
315
+ // Don't display indexer properties, it causes an exception anyway.
316
+ if ( property . GetIndexParameters ( ) . Length > 0 )
317
+ {
318
+ continue ;
319
+ }
320
+
321
+ try
322
+ {
323
+ childVariables . Add (
324
+ new VariableDetails (
325
+ property . Name ,
326
+ property . GetValue ( obj ) ) ) ;
327
+ }
328
+ catch ( Exception ex )
329
+ {
330
+ // Some properties can throw exceptions, add the property
331
+ // name and info about the error.
332
+ if ( ex . GetType ( ) == typeof ( TargetInvocationException ) )
333
+ {
334
+ ex = ex . InnerException ;
335
+ }
336
+
337
+ childVariables . Add (
338
+ new VariableDetails (
339
+ property . Name ,
340
+ new UnableToRetrievePropertyMessage (
341
+ "Error retrieving property - " + ex . GetType ( ) . Name ) ) ) ;
342
+ }
343
+ }
344
+ }
345
+
293
346
#endregion
347
+
348
+ private struct UnableToRetrievePropertyMessage
349
+ {
350
+ public UnableToRetrievePropertyMessage ( string message )
351
+ {
352
+ this . Message = message ;
353
+ }
354
+
355
+ public string Message { get ; }
356
+
357
+ public override string ToString ( )
358
+ {
359
+ return "<" + Message + ">" ;
360
+ }
361
+ }
294
362
}
295
363
}
0 commit comments