Unexected results in Receive-Job - powershell

I noticed weird behavior when using jobs in PowerShell 5.0.
Running job that returns PSObject, also returns some hashtable.
Jobs that return strings, integers, etc work propertly.
Running
Start-Job { New-Object PSObject -Property #{ A = 1 } } |
Receive-Job -Wait -AutoRemoveJob
returns
A : 1
RunspaceId : 6921e85f-301e-4e95-8e4b-c0882fc2085f
PSSourceJobInstanceId : 2992ef77-5642-4eac-8617-f26449a87801
Running
Start-Job { New-Object PSObject } | Receive-Job -Wait -AutoRemoveJob
returns
#{PSComputerName=localhost; RunspaceId=3e783d5f-254a-4951-bb4a-7ff6fa2812c5; PSShowComputerName=False; PSSourceJobInstanceId=1d951dec-0823-4cde-8219-4a6263550441}
However, running
Start-Job { ,#(New-Object PSObject -Property #{ A = 1 }) } |
Receive-Job -Wait -AutoRemoveJob
returns
A
-
1
Why does Receive-Job cmdlet add that hashtable only for PSObjects?
UPDATE: Same in PowerShell 4.0.

PowerShell is not a WYSIWYG shell. Usually, "what you get" is not a text, but objects with properties and methods. And "what you see" is some text representation of them. In many cases by default PowerShell does not display all of the object's properties, but only most common ones, as defined in format files. And some objects use custom formatting, like strings and integers only display their value, but not any of its properties, and collections display their content but not collections themselves.
So, in fact, PowerShell adds extra properties to all objects received from job. But this properties not always get displayed. You can see that extra properties by passing job output to Get-Member cmdlet:
Start-Job { 1,'',#() } | Receive-Job -Wait -AutoRemoveJob | Get-Member
or to formatting cmdlet with appropriate options to force formatting primitive types and to not enumerate collection:
Start-Job { 1,'',#() } | Receive-Job -Wait -AutoRemoveJob | Format-List -Force -Expand CoreOnly

To complement PetSerAl's excellent answer with a focus on the hashtable that wasn't (a hashtable), based on PetSerAl's helpful comments:
The output from Start-Job { New-Object PSObject } | Receive-Job -Wait -AutoRemoveJob,
#{PSComputerName=localhost; RunspaceId=3e783d5f-254a-4951-bb4a-7ff6fa2812c5; PSShowComputerName=False; PSSourceJobInstanceId=1d951dec-0823-4cde-8219-4a6263550441}
only looks like a hashtable literal; in fact, it is the default output formatting for "property-less" custom objects that, in fact, do have properties, but only ones added by PowerShell itself.
This representation is suspiciously similar to a hashtable literal, but the quoting around values that would normally need it is missing - such as around localhost.
Also note that outputting an actual hashtable results in a much nicer, two-column key-value format.
Note that PS still considers a custom object that originally had no properties property-less even after PS itself has added properties to it, such as by Receive-Job here - see below for details.
In its original state (no properties added by PS yet), a property-less object's default output is empty (the empty string). (Try New-Object PSCustomObject directly at the prompt.)
Once Receive-Job has added its "meta" properties to the custom object, their existence triggers the hashtable-like output formatting.
PetSerAl has provided a link to the source code, which suggests that the "PropertyLessObject" formatting is triggered under the following conditions:
An object has no properties at all or only has properties automatically added by PowerShell in the context of remoting (which apparently also includes job-related cmdlets), as done by Receive-Object here.
To put it differently: Properties automatically added by PS aren't taken into account when deciding whether an object is propertyless.
The source-code link will tell you the specific 3-, 4-, or 5-element sets of remoting properties that may be added during remoting and trigger the formatting, but here's a minimal (3-property) example.
Again, note that the hashtable-like formatting is only triggered because the only properties that the object has are named for remoting-related, automatically added properties:
PS> [PSCustomObject] #{PSComputerName='Hi, Mom'; RunspaceId=0; PSShowComputerName=$true}
#{PSComputerName=Hi, Mom; RunspaceId=0; PSShowComputerName=False}
Note that even though a hashtable literal is involved in the command, it is merely used to construct a custom object.
You can force a normal list or table view with Format-List -Force or Format-Table -Force, but note that Boolean property PSShowComputerName never shows up and instead implicitly controls whether the associated PSComputerName property is included in the list / table.
PetSerAl also points out that you can get the hashtable-like output format on demand for any custom object ([pscustomobject]): simply invoke .PSObject.ToString() (note the crucial .PSObject part; without it, you get empty output).
PS> ([pscustomobject] #{ one = 'Hi, Mom'; two = 2 }).PSObject.ToString()
#{one=Hi, Mom; two=2}
Or, more simply, with string interpolation (which, presumably, simply calls .PSObject.ToString() behind the scenes):
PS> "$([pscustomobject] #{ one = 'Hi, Mom'; two = 2 })"
#{one=Hi, Mom; two=2}
Note that this kind of string interpolation does not work for instances of any other types (objects not of type [System.Management.Automation.PSCustomObject]):
PowerShell defers to their .ToString() method even when you invoke .PSObject.ToString().
A directly .NET-based type (e.g., as added with Add-Type) by default simply returns its full type name (both from .ToString() / .PSObject.ToString() and as default output in PS); e.g.:
PS> (Add-Type -PassThru 'namespace net.same2u { public class SomeType {} }')::New()
net.same2u.SomeType # the full type name
The same applies to instances of custom PowerShell classes (defined with class { ... }).

Related

Powershell output PSCustomObject blocks output hashtable [duplicate]

I'm learning PowerShell and a vast number of articles I read strongly discourages the use of write-host telling me it's "bad practice" and almost always, the output can be displayed in another way.
So, I'm taking the advice and try to avoid use of write-host. One suggestion I found was to use write-output instead. As far as I understand, this puts everything in a pipeline, and the output is executed at the end of the script (?).
However, I have problems outputting what I want. This example demonstrates the issue:
$properties = #{'OSBuild'="910ef01.2.8779";
'OSVersion'="CustomOS 3";
'BIOSSerial'="A5FT-XT2H-5A4B-X9NM"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-output $object
$properties = #{'Site'="SQL site";
'Server'="SQL Server";
'Database'="SQL Database"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-Output $object
This way I get a nice output of the first object displaying the OS data, but the second object containing the SQL data is never displayed. I've tried renaming the variable names, and a bunch of other different stuff, but no luck.
While troubleshooting this problem, I found similar problems with suggestions to just replace write-output with write-host. This gets me very confused. Why are some people strongly discouraging write-host, while other people encourage it?
And how exactly do I output these two objects in a fashionably manner? I do not fully understand the pipeline mechanism of write-output.
Just to clarify: the problem is only a display problem:
When outputting to the console, if the first object is table-formatted (if Format-Table is applied, which happens implicitly in your case), the display columns are locked in based on that first object's properties.
Since your second output object shares no properties with the first one, it contributes nothing to the table display and is therefore effectively invisible.
By contrast, if you programmatically process the script's output - assign it to a variable or send its output through the pipeline to another command - both objects will be there.
See Charlie Joynt's answer for a helpful example of assigning the two output objects to separate variables.
The simplest solution to the display problem is to explicitly format for display each input object individually - see below.
For a given single object inside a script, you can force formatted to-display (to-host) output with Out-Host:
$object | Out-Host # same as: Write-Output $object | Out-Host
Note, however, that this outputs directly and invariably to the console only and the object is then not part of the script's data output (the objects written to the success output stream, the stream with index 1).
In other words: if you try to assign the script's output to a variable or send its output to another command in a pipeline, that object won't be there.
See below for why Out-Host is preferable to Write-Host, and why it's better to avoid Write-Host in most situations.
To apply the technique ad hoc to a given script's output as a whole, so as to make sure you see all output objects, use:
./some-script.ps1 | % { $_ | Out-String } # % is the built-in alias of ForEach-Object
Note that here too you could use Out-Host, but the advantage of using Out-String is that it still allows you to capture the for-display representation in a file, if desired.
Here's a simple helper function (filter) that you can put in your $PROFILE:
# Once defined, you can use: ./some-script.ps1 | Format-Each
Filter Format-Each { $_ | Out-String }
PetSerAl's suggestion - ./some-script.ps1 | Format-List - works in principle too, but it switches the output from the usual table-style output to list-style output, with each property listed on its own line, which may be undesired.
Conversely, however, Format-Each, if an output object is (implicitly) table-formatted, prints a header for each object.
Why Write-Output doesn't help:
Write-Output doesn't help, because it writes to where output objects go by default anyway: the aforementioned success output stream, where data should go.
If the output stream's objets aren't redirected or captured in some form, they are sent to the host by default (typically, the console), where the automatic formatting is applied.
Also, use of Write-Output is rarely necessary, because simply not capturing or redirecting a command or expression implicitly writes to the success stream; another way of putting it:
Write-Output is implied.
Therefore, the following two statements are equivalent:
Write-Output $object # write $object to the success output stream
$object # same; *implicitly* writes $object to the success output stream
Why use of Write-Host is ill-advised, both here and often in general:
Assuming you do know the implications of using Write-Host in general - see below - you could use it for the problem at hand, but Write-Host applies simple .ToString() formatting to its input, which does not give you the nice, multi-line formatting that PowerShell applies by default.
Thus, Out-Host (and Out-String) were used above, because they do apply the same, friendly formatting.
Contrast the following two statements, which print a hash-table ([hashtable]) literal:
# (Optional) use of Write-Output: The friendly, multi-line default formatting is used.
# ... | Out-Host and ... | Out-String would print the same.
PS> Write-Output #{ foo = 1; bar = 'baz' }
Name Value
---- -----
bar baz
foo 1
# Write-Host: The hashtable's *entries* are *individually* stringified
# and the result prints straight to the console.
PS> Write-Host #{ foo = 1; bar = 'baz' }
System.Collections.DictionaryEntry System.Collections.DictionaryEntry
Write-Host did two things here, which resulted in near-useless output:
The [hashtable] instance's entries were enumerated and each entry was individually stringified.
The .ToString() stringification of hash-table entries (key-value pairs) is System.Collections.DictionaryEntry, i.e., simply the type name of the instance.
The primary reasons for avoiding Write-Host in general are:
It outputs directly to the host (console) rather than to PowerShell's success output stream.
As a beginner, you may mistakenly think that Write-Host is for writing results (data), but it isn't.
In bypassing PowerShell's system of streams, Write-Host output cannot be redirected - that is, it can neither be suppressed nor captured (in a file or variable).
That said, starting with PowerShell v5.0, you can now redirect its output via the newly introduced information stream (number 6; e.g., ./some-script.ps1 6>write-host-output.txt); however, that stream is more properly used with the new Write-Information cmdlet.
By contrast, Out-Host output still cannot be redirected.
That leaves just the following legitimate uses of Write-Host:
Creating end-user prompts and colored for-display-only representations:
Your script may have interactive prompts that solicit information from the user; using Write-Host - optionally with coloring via the -ForegroundColor and -BackgroundColor parameters - is appropriate, given that prompt strings should not become part of the script's output and users also provide their input via the host (typically via Read-Host).
Similarly, you can use Write-Host with selective coloring to explicitly create friendlier for-display-only representations.
Quick prototyping: If you want a quick-and-dirty way to write status/diagnostic information directly to the console without interfering with a script's data output.
However, it is generally better to use Write-Verbose and Write-Debug in such cases.
Generally speaking the expectation is for script/functions to return a single "type" of object, often with many instances. For example, Get-Process returns a load of processes, but they all have the same fields. As you'll have seen from the tutorials, etc. you can then pass the output of Get-Process along a pipeline and process the data with subsequent cmdlets.
In your case you are returning two different types of object (i.e. with two different sets of properties). PS outputs the first object, but not the second one (which doesn't match the first) as you discovered. If you were to add extra properties to the first object that match those used in the second one, then you'd see both objects.
Write-Host doesn't care about this sort of stuff. The push-back against using this is mainly to do with (1) it being a lazy way to give feedback about script progress, i.e. use Write-Verbose or Write-Debug instead and (2) it being imperfect when it comes to passing objects along a pipeline, etc.
Clarification on point (2), helpfully raised in the comments to this answer:
Write-Host is not just imperfect with respect to the pipeline /
redirection / output capturing, you simply cannot use it for that in
PSv4 and earlier, and in PSv5+ you have to awkwardly use 6>; also,
Write-Host stringifies with .ToString(), which often produces
unhelpful representations
If your script is really just meant to print data to the console then go ahead and Write-Host.
Alternatively, you can return multiple objects from a script or function. Using return or Write-Output, just return object objects comma-separated. For example:
Test-WriteOutput.ps1
$object1 = [PSCustomObject]#{
OSBuild = "910ef01.2.8779"
OSVersion = "CustomOS 3"
BIOSSerial = "A5FT-XT2H-5A4B-X9NM"
}
$object2 = [PSCustomObject]#{
Site = "SQL site"
Server= "SQL Server"
Database="SQL Database"
}
Write-Output $object1,$object2
The run the script, assigning the output into two variables:
$a,$b = .\Test-WriteOutput.ps1
You'll see that $a is $object1 and $b is $object2.
use write-host, write-output is for pipeline (and by default on console after clear)

Displaying Desired output in Powershell Script [duplicate]

This is how my current script looks like:
$cpu = Get-WmiObject win32_processor | select LoadPercentage
logwrite $cpu #this fuction writes $cpu into a .txt file
The output of the file is:
#{LoadPercentage=4}
I want it to be only the number so that I can make calculations.
qbanet359's helpful answer uses direct property access (.LoadPercentage) on the result object, which is the simplest and most efficient solution in this case.
In PowerShell v3 or higher this even works with extracting property values from a collection of objects, via a feature called member-access enumeration.
E.g., ((Get-Date), (Get-Date).AddYears(-1)).Year returns 2019 and 2018 when run in 2019, which are the .Year property values from each [datetime] instance in the array.
In cases where you do want to use Select-Object (or its built-in alias, select), such as when processing a large input collection item by item:
To use Select-Object to extract a single property value, you must use -ExpandProperty:
Get-WmiObject win32_processor | Select-Object -ExpandProperty LoadPercentage
Background:
Select-Object by default creates custom objects ([pscustomobject] instances[1]
) that have the properties you specify via the -Property parameter (optionally implicitly, as the 1st positional argument).
This applies even when specifying a single property[2], so that select LoadPercentage (short for: Select-Object -Property LoadPercentage) creates something like the following object:
$obj = [pscustomobject] #{ LoadPercentage = 4 } # $obj.LoadPercentage yields 4
Because you use Add-Content to write to your log file, it is the .ToString() string representation of that custom object that is written, as you would get if you used the object in an expandable string (try "$([pscustomobject] #{ LoadPercentage = 4 })").
By contrast, parameter -ExpandProperty, which can be applied to a single property only, does not create a custom object and instead returns the value of that property from the input object.
Note: If the value of that property happens to be an array (collection), its elements are output individually; that is, you'll get multiple outputs per input object.
[1] Strictly speaking, they're [System.Management.Automation.PSCustomObject] instances, whereas type accelerator [pscustomobject], confusingly, refers to type [System.Management.Automation.PSObject], for historical reasons; see this GitHub issue.
[2] There's a hotly debated request on GitHub to change Select-Object's default behavior with only a single property; while the discussion is interesting, the current behavior is unlikely to change.
That is a pretty simple fix. Instead of selecting the LoadPercentage when running Get-WmiObject, just select the property when calling your function. This will write only the number to your log file.
$cpulogpath = "C:\Monitoring\$date.csv"
function logwrite
{
param ([string]$logstring)
add-content $cpulogpath -value $logstring
}
$cpu = Get-WmiObject win32_processor #don't select the property here
logwrite $cpu.LoadPercentage #select it here

Please help in converting time zone using powershell when source time is property of an element [duplicate]

This is how my current script looks like:
$cpu = Get-WmiObject win32_processor | select LoadPercentage
logwrite $cpu #this fuction writes $cpu into a .txt file
The output of the file is:
#{LoadPercentage=4}
I want it to be only the number so that I can make calculations.
qbanet359's helpful answer uses direct property access (.LoadPercentage) on the result object, which is the simplest and most efficient solution in this case.
In PowerShell v3 or higher this even works with extracting property values from a collection of objects, via a feature called member-access enumeration.
E.g., ((Get-Date), (Get-Date).AddYears(-1)).Year returns 2019 and 2018 when run in 2019, which are the .Year property values from each [datetime] instance in the array.
In cases where you do want to use Select-Object (or its built-in alias, select), such as when processing a large input collection item by item:
To use Select-Object to extract a single property value, you must use -ExpandProperty:
Get-WmiObject win32_processor | Select-Object -ExpandProperty LoadPercentage
Background:
Select-Object by default creates custom objects ([pscustomobject] instances[1]
) that have the properties you specify via the -Property parameter (optionally implicitly, as the 1st positional argument).
This applies even when specifying a single property[2], so that select LoadPercentage (short for: Select-Object -Property LoadPercentage) creates something like the following object:
$obj = [pscustomobject] #{ LoadPercentage = 4 } # $obj.LoadPercentage yields 4
Because you use Add-Content to write to your log file, it is the .ToString() string representation of that custom object that is written, as you would get if you used the object in an expandable string (try "$([pscustomobject] #{ LoadPercentage = 4 })").
By contrast, parameter -ExpandProperty, which can be applied to a single property only, does not create a custom object and instead returns the value of that property from the input object.
Note: If the value of that property happens to be an array (collection), its elements are output individually; that is, you'll get multiple outputs per input object.
[1] Strictly speaking, they're [System.Management.Automation.PSCustomObject] instances, whereas type accelerator [pscustomobject], confusingly, refers to type [System.Management.Automation.PSObject], for historical reasons; see this GitHub issue.
[2] There's a hotly debated request on GitHub to change Select-Object's default behavior with only a single property; while the discussion is interesting, the current behavior is unlikely to change.
That is a pretty simple fix. Instead of selecting the LoadPercentage when running Get-WmiObject, just select the property when calling your function. This will write only the number to your log file.
$cpulogpath = "C:\Monitoring\$date.csv"
function logwrite
{
param ([string]$logstring)
add-content $cpulogpath -value $logstring
}
$cpu = Get-WmiObject win32_processor #don't select the property here
logwrite $cpu.LoadPercentage #select it here

Is it possible to use a cmdlet as an input parameter for a dynamic PowerShell script?

Here's a test script I'm trying to use, and I'm calling it from a separate process and attempting to pass parameters to it. The idea is that I have a user interface that allows a user to select a CmdLet and then populate another dropdown with the properties/methods of that CmdLet.
My problem seems to be that the script is rendering the input parameter as a string, and is thusly creating a text file with the methods and properties of any arbitrary string to which you've applied a "Get-Member" to, such as "Clone", or "CompareTo". The only property as such is "Length".
Is there any way to have that input parameter be brought over as a usable CmdLet instead of a string? Perhaps I'm missing something, or perhaps what I'm attempting to do isn't possible.
param([string]$inputCmdLet = "Get-NetAdapter");
$wrkgDir = "D:\Distribution\Operational";
# Get Properties and Methods for CmdLet Input Parameter
$propertyNames = $inputCmdLet | Get-Member -MemberType Property;
$methodNames = $inputCmdLet | Get-Member -MemberType Method;
# Sort Arrays
$propertyNames = $propertyNames | Sort-Object Name;
$methodNames = $methodNames | Sort-Object Name;
# Output Results to Text Files
$propertyNames.Name | Out-File $wrkgDir\$inputCmdLet.Properties.txt;
$methodNames.Name | Out-File $wrkgDir\$inputCmdLet.Methods.txt;
EDIT FOR MORE INFO:
The output I'm hoping for, in the example of Get-NetAdapter, is the list of properties in one output file and methods in the other. What I'm getting now is this:
Left list is expected (partial) result, right list is actual result.
I'm uncertain how to achieve the result list on the left (in the image) programmatically. I'm able to get the proper output by typing it out statically:
$mbrNameStatic = Get-NetAdapter | Get-Member;
$mbrNameStatic.Name | Out-File $wrkgDir\$inputCmdLet.Strings.txt;
But when i use the input parameter, it merges the value in as a string, so it seems the actual runtime code looks more like this:
$propertyNames = "Get-NetAdapter" | Get-Member -MemberType Property;
So the addition of the quotes renders the cmdlet as a string (makes sense i suppose, since my input parameter is a string), which returns the properties and methods of a string instead of the cmdlet. Is there any way to have the cmdlet render out without the quotes?
Please do let me know if I'm not making sense with this, either with my description, or with the idea altogether.
Thanks!
In order to execute a command whose name (only) is stored in a variable or whose name is specified in single or double quotes, you must use &, the call operator.
# WRONG: The token is interpreted as an *expression* that outputs a *string*
"Get-NetAdapter" # outputs the [string] literal
# WRONG: ditto, via a variable
$name = "Get-NetAdapter"
$name # outputs the contents of the [string] variable
# OK: Use of & tells Powershell to interpret the next token as a *command* to *invoke*.
& "Get-NetAdapter"
& $name
As for your general approach:
Note that not all cmdlets produce output when invoked without parameters, so your current code (even with &) won't work with all cmdlets.
Conversely, those cmdlets that do produce output when given no parameters may produce a lot of them, which is unnecessary, so consider something like & $inputCmdlet | Select-Object -First 1.
Generically, you can use something like (Get-Command Get-NetAdapter).OutputType to obtain a cmdlet's output type(s), but note that:
Declaring output types is optional, so not all cmdlets may return a value.
If you start with a type rater than an instance of that type, you cannot use Get-Member to discover the instance members (you can only obtain the static members via -Static).

PowerShell: write-output only writes one object

I'm learning PowerShell and a vast number of articles I read strongly discourages the use of write-host telling me it's "bad practice" and almost always, the output can be displayed in another way.
So, I'm taking the advice and try to avoid use of write-host. One suggestion I found was to use write-output instead. As far as I understand, this puts everything in a pipeline, and the output is executed at the end of the script (?).
However, I have problems outputting what I want. This example demonstrates the issue:
$properties = #{'OSBuild'="910ef01.2.8779";
'OSVersion'="CustomOS 3";
'BIOSSerial'="A5FT-XT2H-5A4B-X9NM"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-output $object
$properties = #{'Site'="SQL site";
'Server'="SQL Server";
'Database'="SQL Database"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-Output $object
This way I get a nice output of the first object displaying the OS data, but the second object containing the SQL data is never displayed. I've tried renaming the variable names, and a bunch of other different stuff, but no luck.
While troubleshooting this problem, I found similar problems with suggestions to just replace write-output with write-host. This gets me very confused. Why are some people strongly discouraging write-host, while other people encourage it?
And how exactly do I output these two objects in a fashionably manner? I do not fully understand the pipeline mechanism of write-output.
Just to clarify: the problem is only a display problem:
When outputting to the console, if the first object is table-formatted (if Format-Table is applied, which happens implicitly in your case), the display columns are locked in based on that first object's properties.
Since your second output object shares no properties with the first one, it contributes nothing to the table display and is therefore effectively invisible.
By contrast, if you programmatically process the script's output - assign it to a variable or send its output through the pipeline to another command - both objects will be there.
See Charlie Joynt's answer for a helpful example of assigning the two output objects to separate variables.
The simplest solution to the display problem is to explicitly format for display each input object individually - see below.
For a given single object inside a script, you can force formatted to-display (to-host) output with Out-Host:
$object | Out-Host # same as: Write-Output $object | Out-Host
Note, however, that this outputs directly and invariably to the console only and the object is then not part of the script's data output (the objects written to the success output stream, the stream with index 1).
In other words: if you try to assign the script's output to a variable or send its output to another command in a pipeline, that object won't be there.
See below for why Out-Host is preferable to Write-Host, and why it's better to avoid Write-Host in most situations.
To apply the technique ad hoc to a given script's output as a whole, so as to make sure you see all output objects, use:
./some-script.ps1 | % { $_ | Out-String } # % is the built-in alias of ForEach-Object
Note that here too you could use Out-Host, but the advantage of using Out-String is that it still allows you to capture the for-display representation in a file, if desired.
Here's a simple helper function (filter) that you can put in your $PROFILE:
# Once defined, you can use: ./some-script.ps1 | Format-Each
Filter Format-Each { $_ | Out-String }
PetSerAl's suggestion - ./some-script.ps1 | Format-List - works in principle too, but it switches the output from the usual table-style output to list-style output, with each property listed on its own line, which may be undesired.
Conversely, however, Format-Each, if an output object is (implicitly) table-formatted, prints a header for each object.
Why Write-Output doesn't help:
Write-Output doesn't help, because it writes to where output objects go by default anyway: the aforementioned success output stream, where data should go.
If the output stream's objets aren't redirected or captured in some form, they are sent to the host by default (typically, the console), where the automatic formatting is applied.
Also, use of Write-Output is rarely necessary, because simply not capturing or redirecting a command or expression implicitly writes to the success stream; another way of putting it:
Write-Output is implied.
Therefore, the following two statements are equivalent:
Write-Output $object # write $object to the success output stream
$object # same; *implicitly* writes $object to the success output stream
Why use of Write-Host is ill-advised, both here and often in general:
Assuming you do know the implications of using Write-Host in general - see below - you could use it for the problem at hand, but Write-Host applies simple .ToString() formatting to its input, which does not give you the nice, multi-line formatting that PowerShell applies by default.
Thus, Out-Host (and Out-String) were used above, because they do apply the same, friendly formatting.
Contrast the following two statements, which print a hash-table ([hashtable]) literal:
# (Optional) use of Write-Output: The friendly, multi-line default formatting is used.
# ... | Out-Host and ... | Out-String would print the same.
PS> Write-Output #{ foo = 1; bar = 'baz' }
Name Value
---- -----
bar baz
foo 1
# Write-Host: The hashtable's *entries* are *individually* stringified
# and the result prints straight to the console.
PS> Write-Host #{ foo = 1; bar = 'baz' }
System.Collections.DictionaryEntry System.Collections.DictionaryEntry
Write-Host did two things here, which resulted in near-useless output:
The [hashtable] instance's entries were enumerated and each entry was individually stringified.
The .ToString() stringification of hash-table entries (key-value pairs) is System.Collections.DictionaryEntry, i.e., simply the type name of the instance.
The primary reasons for avoiding Write-Host in general are:
It outputs directly to the host (console) rather than to PowerShell's success output stream.
As a beginner, you may mistakenly think that Write-Host is for writing results (data), but it isn't.
In bypassing PowerShell's system of streams, Write-Host output cannot be redirected - that is, it can neither be suppressed nor captured (in a file or variable).
That said, starting with PowerShell v5.0, you can now redirect its output via the newly introduced information stream (number 6; e.g., ./some-script.ps1 6>write-host-output.txt); however, that stream is more properly used with the new Write-Information cmdlet.
By contrast, Out-Host output still cannot be redirected.
That leaves just the following legitimate uses of Write-Host:
Creating end-user prompts and colored for-display-only representations:
Your script may have interactive prompts that solicit information from the user; using Write-Host - optionally with coloring via the -ForegroundColor and -BackgroundColor parameters - is appropriate, given that prompt strings should not become part of the script's output and users also provide their input via the host (typically via Read-Host).
Similarly, you can use Write-Host with selective coloring to explicitly create friendlier for-display-only representations.
Quick prototyping: If you want a quick-and-dirty way to write status/diagnostic information directly to the console without interfering with a script's data output.
However, it is generally better to use Write-Verbose and Write-Debug in such cases.
Generally speaking the expectation is for script/functions to return a single "type" of object, often with many instances. For example, Get-Process returns a load of processes, but they all have the same fields. As you'll have seen from the tutorials, etc. you can then pass the output of Get-Process along a pipeline and process the data with subsequent cmdlets.
In your case you are returning two different types of object (i.e. with two different sets of properties). PS outputs the first object, but not the second one (which doesn't match the first) as you discovered. If you were to add extra properties to the first object that match those used in the second one, then you'd see both objects.
Write-Host doesn't care about this sort of stuff. The push-back against using this is mainly to do with (1) it being a lazy way to give feedback about script progress, i.e. use Write-Verbose or Write-Debug instead and (2) it being imperfect when it comes to passing objects along a pipeline, etc.
Clarification on point (2), helpfully raised in the comments to this answer:
Write-Host is not just imperfect with respect to the pipeline /
redirection / output capturing, you simply cannot use it for that in
PSv4 and earlier, and in PSv5+ you have to awkwardly use 6>; also,
Write-Host stringifies with .ToString(), which often produces
unhelpful representations
If your script is really just meant to print data to the console then go ahead and Write-Host.
Alternatively, you can return multiple objects from a script or function. Using return or Write-Output, just return object objects comma-separated. For example:
Test-WriteOutput.ps1
$object1 = [PSCustomObject]#{
OSBuild = "910ef01.2.8779"
OSVersion = "CustomOS 3"
BIOSSerial = "A5FT-XT2H-5A4B-X9NM"
}
$object2 = [PSCustomObject]#{
Site = "SQL site"
Server= "SQL Server"
Database="SQL Database"
}
Write-Output $object1,$object2
The run the script, assigning the output into two variables:
$a,$b = .\Test-WriteOutput.ps1
You'll see that $a is $object1 and $b is $object2.
use write-host, write-output is for pipeline (and by default on console after clear)