question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Allow more control on custom type formatting

See original GitHub issue

Summary of the new feature/enhancement

A developer might have created some custom type that aims to behave like a primitive or built-in type, but PowerShell does not provide a way to let the developer precisely control formatting of custom types. In particular, the handling of string is very special — it implements IEnumerable<char>, but will not be enumerated (into a stream of chars) when formatted. There is no way for a custom type to be handled like a string.

Another example. Currently, System.Text.Rune does not have special handling, and the default formatting is Format-List instead of ToString(). Arguably, ToString() is more appropriate. For a custom type, the only way to force ToString() formatting is to apply System.Management.Automation.HiddenAttribute to all instance fields and properties. However, this comes with the downside of referencing PowerShell in the assembly containing the type, but the assembly should be usable even in an environment without PowerShell (and you don’t want to load PowerShell assemblies if you aren’t using it anyway).

The closest we can get for Rune (or any other custom type not implementing IEnumerable nor IEnumerator) is to give it a Custom view, but it will add blank lines when outputting. Not ideal…

image

This issue aims to generate discussion on creating mechanisms to have fine-grained control over (default) formatting of a type. Related #9538.

Proposed technical implementation details (optional)

Here is a simple proposal.

  • Decouple Out-Default into Format-Default and Out-Host. Today’s PowerShell handles Out-Default monolithically. It chooses Format-Table or Format-List or ToString()-formatting, depending on the number of non-hidden public fields and properties, and then applies Out-Host.
  • In Format.ps1xml, allow a new type of NativeControl. It specifies a type name, which, if found in loaded assemblies, is used to format the object.
  • Format-Default chooses the format as follows:
    1. If there is a view defined for this type, use the first available view (which does not have to be NativeControl view). However, if NativeControl formatter was used to produce the object being formatted (formatting is recursive for NativeControl views), then NativeControl is skipped.
    2. Otherwise, use today’s default formatting choice. (Special handling for string; use ToString() if there is no non-hidden public fields or properties; use Format-Table if there are only a few; use Format-List if there are many.)

Make the types in Microsoft.PowerShell.Commands.Internal.Format public, so people can create custom format objects. Provide a new abstract class:

// or another namespace?
namespace System.Management.Automation
{
  // The derived class must be stateless, so that
  // only one instance is created per PowerShell runspace.
  public abstract class NativeFormatter
  {
    // The returned object is recursively formatted,
    // but cannot be sent to the same formatter again.
    public abstract object FormatObject(object obj);
  }
}

For example, suppose we have a custom string class AsciiString, then we can create

public sealed class AsciiStringFormatter : NativeFormatter
{
  public override object FormatObject(object obj) { return ((AsciiString)obj).ToString(); }
}

This will format an AsciiString into a string, which will then be handled by PowerShell natively.

It’s also possible to return IEnumerable from FormatObject, which will be expanded by PowerShell, and each resulting object is formatted by their own formatter. Those expanded object could be what is Microsoft.PowerShell.Commands.Internal.Format.GroupEntryData today.

Why it is important that we implement formatting in another class.

  • First, we don’t want the main assembly (e.g., the assembly containing AsciiString) to be tied to PowerShell. By allowing it to be implemented in another class, the author can implement the formatter in a separate assembly dedicated for scripting (a kind of add-on to the main assembly).
  • Second, users (non-developers of the main assembly) can develop their own native formatter if they so wish.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
GeeLawcommented, Mar 28, 2021

According to the docs, every line of command is equivalent when appended with | Out-Host, so if an object is enumerable, it will only be treated as the enumerated items, not itself.

When scripting, there really isn’t much difference between List<byte> and byte[] (and even object[] with all items being boxed bytes). I wouldn’t expect a cmdlet/function that happens to use List as its intermediate storage and direct return value to be formatted differently. But there is a crucial difference between AsciiString : IEnumerable<byte> and byte[]. I would lean towards implementing the first aspect, and leaving the second as-is (an enumerable type cannot be formatted, unless the enumeration depth is exhausted).

1reaction
GeeLawcommented, Mar 27, 2021

@mklement0 Thanks for the pointer! I didn’t know OutOfBand (looks like this feature is little documented). This definitely solves the problem for non-enumerable types. Yet the issue for enumerable types still stand.

Currently, it’s not possible to prevent enumeration unless the type is string or the enumeration depth is exhausted.

@'
<Configuration><ViewDefinitions><View>
<Name>MyHashTableFormat</Name>
<OutOfBand />
<ViewSelectedBy><TypeName>System.Collections.Hashtable</TypeName></ViewSelectedBy>
<CustomControl><CustomEntries><CustomEntry><CustomItem><ExpressionBinding><ScriptBlock>
ConvertTo-Json -InputObject $_ -Compress
</ScriptBlock></ExpressionBinding></CustomItem></CustomEntry></CustomEntries></CustomControl>
</View></ViewDefinitions></Configuration>
'@ > ($tmpFile = [IO.Path]::GetTempPath() + "$PID.ps1xml");
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser -Force;
Update-FormatData -PrependPath $tmpFile;
Remove-Item $tmpFile;

$ht = @{'a' = 1; 'b' = 'c'};
$array1 = [object[]]@(0);
$array2 = [object[]]@(0);
$array1[0] = $ht;
$array2[0] = $array1;

$array1; # $ht is enumerated
$array2; # uses the custom format
Read more comments on GitHub >

github_iconTop Results From Across the Web

7 Amazing Excel Custom Number Format Tricks (you Must ...
Using Excel Custom Number Formatting, you can display the data in the desired format. Here are six Excel Custom Number Format tricks for...
Read more >
7 Clever Excel Custom Formatting Tricks (Advanced) - YouTube
Custom formatting allows you to show the cell content in a ... (or replace 0's with text ) 05:50 Conditional Custom Formatting (Show...
Read more >
Why you SHOULD be USING Custom Number Formatting in ...
Also would have been good to mention that you could write Excel's color numbers, like [color30] instead of [red], that seems to be...
Read more >
Format a text field
Custom formats for text fields in Access can be useful when you want the data displayed in a specific way.
Read more >
Custom types and std::format from C++20
std::format is a large and powerful addition in C++20 that allows us to format text into strings efficiently.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found