ProfK
ProfK

Reputation: 51094

Why is text output from one CmdLet formatted and text output from another is not?

I have written 2 cmdlets, for converting between GUIDs and Oracle's Guid equivalent, the strings it displays for RAW columns used to store GUIDs. The first, Convert-GuidToRaw, accepts a GUID string param and outputs a raw string:

var raw = GuidConverter.Core.GuidConverter.ToRaw(Input);
WriteObject(raw);

Where raw has type string. When I run this cmdlet, I get a plain and simple string output:

PS D:\SANRAL\NRA2> New-Guid | Convert-GuidToRaw                                                                  
DD8386EE09231A43B7731880CCAD6B87
PS D:\SANRAL\NRA2>

My other cmdlet, Convert-RawToGuid, accepts a RAW string param representing a GUID and outputs a raw GUID:

var guid = GuidConverter.Core.GuidConverter.FromRaw(Input);
WriteObject(guid);

Where guid has type Guid. When I run this cmdlet, I get output formatted like a table:

PS D:\SANRAL\NRA2> Convert-RawToGuid DD8386EE09231A43B7731880CCAD6B87

Guid
----
ee8683dd-2309-431a-b773-1880ccad6b87


PS D:\SANRAL\NRA2>

In both cases I output a single object, not a list, and in both cases the object is easily represented by a simple string. Why do I get tabular output when I return a Guid type?

It may be worth noting that the Convert-GuidToRaw cmdlet, with its plain, un-formatted, string output, doesn't seem to properly write to the pipeline. Both cmdlets take one parameter, from the pipeline. I would expect this cmdlet pipeline below to output a GUID, when instead the last cmdlet in the pipeline is till looking for input:

PS C:\WINDOWS\system32> New-Guid | Convert-GuidToRaw | Convert-RawToGuid

cmdlet Convert-RawToGuid at command pipeline position 3
Supply values for the following parameters:
Input:

Why is Convert-RawToGuid not getting a "pipelined" string from Convert-GuidToRaw? Swapping the order of the pipeline as below, causes the expected behaviour:

PS C:\WINDOWS\system32> Convert-RawToGuid F3BD9411DE8E4F4BBCACECFCED6D305D | Convert-GuidToRaw
F3BD9411DE8E4F4BBCACECFCED6D305D
PS C:\WINDOWS\system32>

The Convert-RawToGuid cmdlet looks like this:

[Cmdlet(VerbsData.Convert, "RawToGuid")]
public class ConvertRawToGuidCommand : System.Management.Automation.Cmdlet
{
    [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true)]
    public string Input { get; set; }

    protected override void ProcessRecord()
    {
        if (string.IsNullOrEmpty(Input) || Input.Length != 32)
        {
            throw new ArgumentException("Input must be a 32 character hex string");
        }
        var guid = GuidConverter.Core.GuidConverter.FromRaw(Input);
        WriteObject(guid);
    }
}

The Convert-GuidToRaw cmdlet looks like this:

[Cmdlet(VerbsData.Convert, "GuidToRaw")]
public class ConvertGuidToRawCommand : System.Management.Automation.Cmdlet
{
    [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true)]
    public Guid Input { get; set; }

    protected override void ProcessRecord()
    {
        var raw = GuidConverter.Core.GuidConverter.ToRaw(Input);
        WriteObject(raw);
    }
}

Upvotes: 0

Views: 297

Answers (1)

mclayton
mclayton

Reputation: 10075

Part 1

In both cases I output a single object, not a list, and in both cases the object is easily represented by a simple string. Why do I get tabular output when I return a Guid type?

When a command in an interactive session returns an unassigned value, PowerShell passes it to a default Format Command to generate the text before it's written to the console. PowerShell is pre-configured with a bunch of Format Commands for some specific types, and System.Guid is one of those types.

For example:

PS> [Guid]::NewGuid()

Guid
----
ee737b6f-7f68-4015-8841-1278b37a6420

The default formatters for various types are configured in *.Format.ps1xml files in PowerShell 5.1, and are baked into the source code from PowerShell 6 onwards as per About Format.ps1xml

In PowerShell 5.1, the default formatter configuration for System.Guid can be found in $PSHOME\DotNetTypes.format.ps1xml and looks like this:

<View>
  <Name>System.Guid</Name>
  <ViewSelectedBy>
    <TypeName>System.Guid</TypeName>
  </ViewSelectedBy>
  <TableControl>
    <TableRowEntries>
      <TableRowEntry>
        <TableColumnItems>
          <TableColumnItem>
            <PropertyName>Guid</PropertyName>
          </TableColumnItem>
        </TableColumnItems>
      </TableRowEntry>
    </TableRowEntries>
  </TableControl>
</View>

so you get a table with a single column that contains the Guid property.

You get exactly the same result if you do this:

PS> [Guid]::NewGuid() | Format-Table -Property "Guid"

Guid
----
ee737b6f-7f68-4015-8841-1278b37a6420

and if you want to capture the result of the default formatting command into a string variable (e.g. to write to a log file) you can do this:

PS> $text = [Guid]::NewGuid() | Out-String
PS> $text

Guid
----
ee737b6f-7f68-4015-8841-1278b37a6420

Part 2

Why is Convert-RawToGuid not getting a "pipelined" string from Convert-GuidToRaw? Swapping the order of the pipeline as below, causes the expected behaviour:

I'm not able to reproduce your issue, but the following works for me (with the caveat that I couldn't find a library with the the class GuidConverter.Core.GuidConverter so I'm assuming it's a private implementation and I'm using the default string fomat instead - e.g. : "024673b5-9c65-447a-be87-dae5f11f5142"). You could try this locally and see if you get the same result or the issue you reported, and go from there...

  • Create a new C# Class Library solution using .Net Framework 4.7.2 in Visual Studio
  • Add a reference to the Microsoft.PowerShell.5.1.ReferenceAssemblies NuGet package
  • Add the code below into a new class file and build the project:
using System;
using System.Management.Automation;

namespace MyCmdlet
{

    [Cmdlet(VerbsData.Convert, "RawToGuid")]
    public class ConvertRawToGuidCommand : Cmdlet
    {
        [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true)]
        public string Input { get; set; }
        protected override void ProcessRecord()
        {
            if (string.IsNullOrEmpty(this.Input) || this.Input.Length != 36)
            {
                throw new ArgumentException("Input must be a 36 character hex string");
            }
            var guid = new Guid(this.Input);
            WriteObject(guid);
        }
    }

    [Cmdlet(VerbsData.Convert, "GuidToRaw")]
    public class ConvertGuidToRawCommand : Cmdlet
    {
        [Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true)]
        public Guid Input { get; set; }
        protected override void ProcessRecord()
        {
            var raw = this.Input.ToString();
            WriteObject(raw);
        }
    }

}

If I build this code I can reference it from PowerShell and the following code works:

PS> Import-Module ".\MyCmdlet.dll"
PS> New-Guid | Convert-GuidToRaw | Convert-RawToGuid

Guid
----
15591624-44c9-4c55-acd7-22b7a2d0bee0

Upvotes: 1

Related Questions