Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ internal GridHeader(OutGridViewCommand parentCmd)
internal static GridHeader ConstructGridHeader(PSObject input, OutGridViewCommand parentCmd)
{
if (DefaultScalarTypes.IsTypeInList(input.TypeNames) ||
!OutOfBandFormatViewManager.HasNonRemotingProperties(input))
OutOfBandFormatViewManager.IsPropertyLessObject(input))
{
return new ScalarTypeHeader(parentCmd, input);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,19 +465,46 @@ private static ViewGenerator SelectViewGeneratorFromProperties(FormatShape shape
/// </summary>
internal static class OutOfBandFormatViewManager
{
internal static bool IsPropertyLessObject(PSObject so)
{
// Using an enumerator to avoid the ExpandAll costs
var propertiesEnumerator = so.Properties.GetEnumerator();

try
{
// Manually enumerate properties - Maximum iterations : 6 (5 Remoting properties + 1)
while (propertiesEnumerator.MoveNext())
{
var property = propertiesEnumerator.Current;

// Skip remoting properties
if (IsNotRemotingProperty(property.Name))
{
// Found a non-remoting property - object is NOT propertyless
return false;
}
}

// Only remoting properties found (or no properties at all)
return true;
}
finally
{
// Clean up the enumerator if it's IDisposable
(propertiesEnumerator as IDisposable)?.Dispose();
}
}

private static bool IsNotRemotingProperty(string name)
{
var isRemotingPropertyName = name.Equals(RemotingConstants.ComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase)
|| name.Equals(RemotingConstants.ShowComputerNameNoteProperty, StringComparison.OrdinalIgnoreCase)
|| name.Equals(RemotingConstants.RunspaceIdNoteProperty, StringComparison.OrdinalIgnoreCase)
|| name.Equals(RemotingConstants.SourceJobInstanceId, StringComparison.OrdinalIgnoreCase);
|| name.Equals(RemotingConstants.SourceJobInstanceId, StringComparison.OrdinalIgnoreCase)
|| name.Equals(RemotingConstants.EventObject, StringComparison.OrdinalIgnoreCase);
return !isRemotingPropertyName;
}

private static readonly MemberNamePredicate NameIsNotRemotingProperty = IsNotRemotingProperty;

internal static bool HasNonRemotingProperties(PSObject so) => so.GetFirstPropertyOrDefault(NameIsNotRemotingProperty) != null;

internal static FormatEntryData GenerateOutOfBandData(TerminatingErrorContext errorContext, PSPropertyExpressionFactory expressionFactory,
TypeInfoDataBase db, PSObject so, int enumerationLimit, bool useToStringFallback, out List<ErrorRecord> errors)
{
Expand All @@ -503,8 +530,9 @@ internal static FormatEntryData GenerateOutOfBandData(TerminatingErrorContext er
}
else
{
// If we have a scalar type, or no visible properties, we fallback to a ToString call
if (DefaultScalarTypes.IsTypeInList(typeNames)
|| !HasNonRemotingProperties(so))
|| IsPropertyLessObject(so))
{
// we force a ToString() on well known types
return GenerateOutOfBandObjectAsToString(so);
Expand All @@ -515,12 +543,6 @@ internal static FormatEntryData GenerateOutOfBandData(TerminatingErrorContext er
return null;
}

// we must check we have enough properties for a list view
if (new PSPropertyExpression("*").ResolveNames(so).Count == 0)
{
return null;
}

// we do not have a view, we default to list view
// process an out of band view as a default
outOfBandViewGenerator = new ListViewGenerator();
Expand Down
21 changes: 21 additions & 0 deletions test/powershell/engine/Formatting/BugFix.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,27 @@ Describe "Hidden properties should not be returned by the 'FirstOrDefault' primi
$outstring.Trim() | Should -BeLike "*.Hidden2"
}

It "Formatting for an object with only hidden property should use 'ToString' after a Get-Member call" {
class Hidden {
hidden $Param = 'Foo'
[String]ToString() { return 'MyString' }
}

$hiddenObjectOne = [Hidden]::new()
$hiddenObjectOne | Get-Member | Out-Null
$outstring = $hiddenObjectOne | Out-String
$outstring.Trim() | Should -BeExactly "MyString"

class Hidden2 {
hidden $Param = 'Foo'
}

$hiddenObjectTwo = [Hidden2]::new()
$hiddenObjectTwo | Get-Member | Out-Null
$outstring = $hiddenObjectTwo | Out-String
$outstring.Trim() | Should -BeLike "*.Hidden2"
}

It 'Formatting for an object with no-hidden property should use the default view' {
class Params {
$Param = 'Foo'
Expand Down