Reputation: 8892
I have a function Get-Projects
that returns an array of objects. I want to print these to the console so the user can select which project they are interested in. However, I have four scenarios only two of which print the desired/expected result.
Scenario 1 - Tabular Output, No Function
When I simply "return" the projects like so they are printed out as expected in tabular fashion. This is the desired format.
$projects = Get-Projects
$projects
# Console Output
id name children
-- ---- --------
1 Project 1 1 {@id=2; name=Project 2}
3 Project 3 3 {@id=4; name=Project 4}
Scenario Two - No Output w/ Write-Projects Function
I created a function named Write-Projects
to encapsulate the formatting behavior in case I decide to change how the formatting down the road. Yet, when I do this nothing is printed to the console.
Function Write-Projects
{
Param([Object[]] $projects)
$projects
}
$projects = Get-Projects
Write-Projects $projects
# No Console Output
Scenario 3 - String Output w/ Write-Projects Function
If I modify Write-Projects to use Write-Host $projects
I do get console output but not what I expected. It's appears to be the string representation of my Object array.
Function Write-Projects
{
Param([Object[]] $projects)
Write-Host $projects
}
$projects = Get-Projects
Write-Projects $projects
# Console Output
@{id=1; name=Project 1; children=System.Object[]} @{id=2; name=Project 2; children=System.Object[]}
Scenario 4 - Tabular Output w/ Write-Projects Function
I discovered this question which solves the problem but I am uncertain why. Essentially my Write-Projects method now looks like this.
Function Write-Projects
{
Param([Object[]] $projects)
Write-Host ($projects | Format-Table | Out-String)
}
$projects = Get-Projects
Write-Projects $projects
# Console Output
id name children
-- ---- --------
1 Project 1 1 {@id=2; name=Project 2}
3 Project 3 3 {@id=4; name=Project 4}
What is happening in each of these scenarios and why I am getting the output as described?
Upvotes: 1
Views: 818
Reputation: 1063
I am not sure why scenario 2 doesn't work. Just to test I made some custom objects and tried and it does work for me:
PS> $obj1 = New-Object PSObject @{ a = 1; b = 2 }
PS> $obj2 = New-Object PSObject @{ a = 3; b = 4 }
PS> $obj1,$obj2
Name Value
---- -----
a 1
b 2
a 3
b 4
PS> function write-projects { param ([object[]] $projects) $projects }
PS> write-projects -projects $obj1,$obj2
Name Value
---- -----
a 1
b 2
a 3
b 4
I know why scenario 3 does not work. It's because when you use Write-Host
the object is converted to a text representation using its toString()
method and so that's why your object isn't output as it is:
PS> function write-projects { param ([object[]] $projects) write-host $projects }
PS> write-projects -projects $obj1,$obj2
System.Collections.Hashtable System.Collections.Hashtable
# notice the output of toString()
PS> $obj1.ToString()
System.Collections.Hashtable
A better approach is to use Write-Output
as (a) this enumerates the objects automatically, and (b) it's "pipeline-friendly" in that the object is not written to the host directly but passed on to the next step.
PS> function write-projects { param ([object[]] $projects) write-output $projects }
PS> write-projects -projects $obj1,$obj2
Name Value
---- -----
a 1
b 2
a 3
b 4
And the reason why scenario 4 works is because you are outputting the object directly - implicitly enumerating it - and then it's formatted and the formatted output is what Write-Host
receives to emit to the screen. I would say you can skip Write-Host
in scenario 4 and it should still work.
PS> function write-projects { param ([object[]] $projects) $projects | format-table }
PS> write-projects -projects $obj1,$obj2
Name Value
---- -----
a 1
b 2
a 3
b 4
Hope that helps!
Upvotes: 2
Reputation: 8367
This question has a simple answer and a complex solution:
Don't use Write-Host (except in a formatter)
Write-Host outputs directly to the screen, which prevents you from ever using the data. You ideally want Get-Projects to display the projects the way you want, and still be a real object. You can do this with a formatter.
What's happening when you Write-Host in the first scenario is that PowerShell is coercing the input (a list of objects) into a string.
In the second example, you're running into a nested version of the same bug: Children is being coerced into a string, so that it works for Format-Table.
The correct way to do this would be with a formatter.
There are a few ways to go about making this, but I'd recommend a module I wrote a couple of years ago called EzOut. EZOut lets you use Cmdlets to write formatters, and lets you add them dynamically.
To make this work, modify each output of Get-Projects like so:
$output.pstypenames.clear()
$output.pstypenames.add("Project")
I'd then recommend importing EZOut and writing a custom formatter:
Write-Format -TypeName "Project" -Action {
$project = $_
"
Project Name : $($project.Name)
ID : $($project.ID)
Children $(
$(
$project.Children | format-Table | Out-String | Foreach-Object { " " + $_ + "
"}
)
"
} |
Out-FormatData |
Add-FormatData
This will create a formatter that (should) display the children indented, and will register it.
Upvotes: 0