Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
183 views
in Technique[技术] by (71.8m points)

powershell - How to create an Object that outputs a List (Not A Table)

This is a spin-off from another question which is located here: Formatting an Object as a neatly looking list The basis of the argument, I believe, was wrong since we're not dealing with formatting of the object afterwards. That's only for looks as shown by the console but it can have bearing on the integrity of the object as you manipulate the variable containing the object.

What I need is to create an object that inherently outputs a list (not a table). I know it's possible because I've tested many functions that I've not written and the objects created are in fact lists. No need to use Format-List to distort or shape what's already there. I just have not being able to figure out why sometimes the output is a list or a table. I'm not sure where the magic is. However I do know that when I run $Host before I run the variable containing the created object I get the object that Host produces, which a list, and that shapes the object afterwards also as a list that normally would be shown as a table. Of course, that may give the result I want but I'm not looking to show the Host information. So what is the solution to this, I wish someone could explain this.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

PowerShell does some default formatting when it presents data/objects to the user. Usually objects are displayed in tabular form when they have up to 4 properties, and in list form when they have more than 4 properties.

If you output several things in a row, PowerShell applies the format (list/table) from the first object to all subsequent objects. I don't know the exact reasoning behind this behavior, but presumably it's to make the output more consistent.

Demonstration:

PS C:> $o1 = New-Object -Type PSObject -Property @{a=1;b=2;c=3;d=4;e=5}
PS C:> $o2 = New-Object -Type PSObject -Property @{x='foo';y='bar'}
PS C:> $o1

c : 3
e : 5
d : 4
b : 2
a : 1

PS C:> $o2

y                                    x
-                                    -
bar                                  foo

PS C:> $o1; $o2

c : 3
e : 5
d : 4
b : 2
a : 1

y : bar
x : foo

Beware, however, that relying on this behavior might lead to undesired results if you output objects in the wrong order:

PS C:> $o2; $o1

y                                    x
-                                    -
bar                                  foo         # ← properties of $o2
                                                 # ← empty line for $o1!

$o1 appears as a blank line in the above output, because outputting $o2 first establishes tabular output format with the columns y and x, but $o1 doesn't have these properties. Missing properties are displayed as blank values in tabular output, whereas additional properties are omitted from the output. There are also cases where you might get the output from the second object/list in list form (run for instance Get-Process; Get-ChildItem in a PowerShell console).

You can force subsequent objects or object arrays to be displayed as separate tables (or lists) by piping them through the Format-Table (or Format-List) cmdlet:

PS C:> $o2; $o1 | Format-Table

y                                    x
-                                    -
bar                                  foo


              c            e            d            b            a
              -            -            -            -            -
              3            5            4            2            1

You can also force PowerShell to display each variable individually by piping them through (for instance) Out-Default:

PS C:> $o2 | Out-Default; $o1 | Out-Default

y   x
-   -
bar foo

c : 3
e : 5
d : 4
b : 2
a : 1

Note, however, that this writes to the console, so the resulting output can't be captured, redirected, or pipelined anymore. Use this only if you want to display something to a user.

For additional information about PowerShell output formatting see here.


There are ways to change the default behavior of how an object is displayed, but unfortunately they're not exactly straightforward. For one thing you can define a default display property set to have PowerShell display not all properties, but just a particular subset.

PS C:> $props = 'c', 'd'
PS C:> $default = New-Object Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$props)
PS C:> $members = [Management.Automation.PSMemberInfo[]]@($default)
PS C:> $o1 | Add-Member MemberSet PSStandardMembers $members
PS C:> $o1

                c                    d
                -                    -
                3                    4

You can still get all properties displayed by using Format-List *:

PS C:> $o1 | Format-List *

c : 3
e : 5
d : 4
b : 2
a : 1

Defining the default display property set doesn't allow you to define the output format though. To do that you probably need to write a custom formatting file. For that to work you probably also need to define a custom type for your objects.

$formatFile = "$HOMEDocumentsWindowsPowerShellYour.Format.ps1xml"
$typeFile   = "$HOMEDocumentsWindowsPowerShellYour.Type.ps1xml"

@'
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
  <ViewDefinitions>
    <View>
      <Name>Default</Name>
      <ViewSelectedBy>
        <TypeName>Foo.Bar</TypeName>
      </ViewSelectedBy>
      <ListControl>
        ...
      </ListControl>
    </View>
  </ViewDefinitions>
</Configuration>
'@ | Set-Content $formatFile
Update-FormatData -AppendPath $formatFile

@'
<?xml version="1.0" encoding="utf-8" ?>
<Types>
  <Type>
    <Name>Foo.Bar</Name>
    <Members>
      ...
    </Members>
  </Type>
</Types>
'@ | Set-Content $typeFile
Update-TypeData -AppendPath $typeFile

$o2.PSTypeNames.Insert(0, 'Foo.Bar')

Jeffrey Hicks wrote an article series on the subject that you may want to read.


With all of that said, I would not recommend going this route unless you have very compelling reasons to do so. I tried to explain it before, but @TesselatingHeckler put it much more concisely than me, so I'm going to quote him on this:

PowerShell is not bash, it has a separation of content and presentation, like HTML and CSS have.

What you normally want to do in PowerShell is keep your data in objects, and have the properties of these objects contain the "raw" (i.e. unformatted) data. That gives you the most flexibility for processing your data. Formatted data usually only gets in the way, because it forces you to parse/convert the data again. Format your data only when you need to display it to a user, and use the Format-* cmdlets to do so. And if your output is intended for further processing: don't bother formatting it in the first place. Leave it to the user how he wants to display the data.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...