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