How to compare JSON in powershell - powershell

I have a requirement where I need to compare JSON object from a file to the JSON message which comes into Anypoint MQ queue. I am able to get the message from the queue. I have used below script but it is not working. I did both -eq and Compare-Object but they are not working.
$po_ps_output = $filemessagecontent | ConvertFrom-Json
$po_python_output = $mqmessagecontent.body | ConvertFrom-Json
$result = $po_ps_output -eq $po_python_output

If you just want to know if the two JSON-originated objects differ, without needing to know how:
$contentEqual = ($po_ps_output | ConvertTo-Json -Compress) -eq
($po_python_output | ConvertTo-Json -Compress)
Note:
ConvertTo-Json defaults to a serialization depth of 2 - use -Depth <n> if your data is more deeply nested to avoid truncation (potentiall data loss) - see this post.
Converting back to JSON may seem like an unnecessary step, but the -Compress standardizes the output formatting to a single line with no extra whitespace, which ensures that incidental variations in formatting in the input (if you had used the input JSON text directly) are ignored.
If you want to know how the two JSON-originated objects differ:
Note: The following is only useful in the following, limited scenario -
a generic, robust solution would require much more effort:
The inputs have the same structure and differ only in property names / values.
The ordering of (equivalent) properties is the same.
Compare-Object (($po_ps_output | ConvertTo-Json) -split '\r?\n') `
(($po_python_output | ConvertTo-Json) -split '\r?\n')
The output will show the lines that differ, each representing a single property or primitive value; e.g.:
InputObject SideIndicator
----------- -------------
"DOB": "12-03-1994" =>
"DOB": "12-03-1999" <=
Note:
=> / <= indicate that the line is unique to the RHS / LHS.
Again, the explicit reconversion to JSON is done to ensure uniform formatting; in this case, a line-oriented pretty-printed format that enables property-by-property comparison.
Again, you may have to use -Depth to prevent truncation of data.
For interactive inspection of differences, you can try a difference-visualization tool, such as the one built into Visual Studio Code, by passing the two JSON strings in pretty-printed form via files to code --diff <file1> <file2>.
As for what you tried:
ConvertFrom-Json creates [pscustomobject] instances, so you're comparing two instances of that type:
If you use -eq, reference equality is tested for, because [pscustomobject] is a reference type and doesn't implement custom equality comparison.
Therefore, $po_ps_output -eq $po_python_output will only be $true if the two variables point to the very same object in memory - which is clearly not the case here, so you'll always get $false.
If you use Compare-Object, the two instances are compared by their .ToString() values.
As of PowerShell Core 7.0.0-preview.4, regrettably, calling .ToString() on an instance of [pscustomobject] yields the empty string (''), which should be considered a bug - see this GitHub issue.
Therefore, Compare-Object $po_ps_output $po_python_output (unhelpfully) considers the two instances equal and returns nothing, since equal objects are by default not output (use -IncludeEqual to include them).

Related

Hash Table (Filter, Values, etc...)

If I run the following, I get the following results.
[string[]]$p = Get-Process Explorer |Select-Object -Property ID,Name,Handles,MainWindowTitle
$p
foreach($item in $p)
{
Write-Output $item
}
#{Id=3560; Name=explorer; Handles=1454; MainWindowTitle=C:\}
#{Id=3868; Name=explorer; Handles=2787; MainWindowTitle=Y:\}
#{Id=9468; Name=explorer; Handles=1823; MainWindowTitle=Z:\}
Question 01: Is this considered to be a hashtable?
Question 02: How can I get the values of a key such as MainWindowTitle?
Question 03: How would I filter on ID field ending with 68?
Question 01: Is this considered to be a hashtable?
No: you're explicitly creating a string array ([string[]]), which means that the [pscustomobject] instances emitted by Select-Object, based on the [System.Diagnostics.Process] instances that Get-Process outputs, are stringified.
A stringified [pscustomobject] uses a hashtable-like representation that is unrelated to actual hashtables - see this answer.
Note:
This is a for-display, string representation that is not suitable for programmatic processing, notably because information about the data types of properties and boundaries around string property values with spaces are lost.
For that reason, in general, avoid dealing with string representations of objects in favor of processing objects as-is, which enables robust, property-based access.
Question 02: How can I get the values of a key such as MainWindowTitle?
Use the [System.Diagnostics.Process] objects output by Get-Process as-is, which allows you to access their properties (no need for an intermediate Select-Object call); the following example uses member-access enumeration to retrieve all .MainWindowTitle property values:
(Get-Process Explorer).MainWindowTitle
Question 03: How would I filter on ID field ending with 68?
Building on the above, using Where-Object with simplified syntax for filtering:
Get-Process Explorer | Where-Object ID -like *68

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)

Powershell how to subtract a variable from another variable, both contain the Exchange Mailbox Object Identity

I have two variables both which contain the Get-Mailbox object "Identity" of user accounts. I need to subtract the contents of one from the other IE:
$termednofwd = (domain.local/OUname/SubOU/Users/first1 last1, domain.local/OUname/SubOU/first2 last2)
$termedfmr = (domain.local/OUname/SubOU/Users/first1 last1)
I want something that would subtract the contents of $termedfmr from $termednofwd giving something like the below. Compare-Object only lists the contents that are in both, I basically need to subtract what is in both from the first variable.
essentially:
$termednofwdnofmr = $termednofwd - $termedfmr resulting in this:
$termednofwdnofmr = (domain.local/OUname/SubOU/first2 last2)
In set theory terms, you're looking for the relative complement between two collections, which Compare-Object can provide, although it requires additional effort:
By default, Compare-Object provides the symmetric difference between two sets, i.e. it lists the union of relative complements; that is, given two sets A and B, it lists both those elements of B not present in A and those elements of A not present in B, and it uses the .SideIndicator property to indicate which is which:
'<=' indicates objects unique to set A (-ReferenceObject argument, or first positional argument), whereas
'=>' indicates elements unique to set B (-DifferenceObject argument, or second positional argument).
Therefore, you need to filter the output objects by their .SideIndicator values.
The -PassThru switch additionally ensures that the input objects are passed through (as opposed to wrapping them in a [pscustomobject] instance whose .InputObject contains them):
$termednofwd = 'domain.local/OUname/SubOU/Users/first1 last1',
'domain.local/OUname/SubOU/first2 last2'
$termedfmr = 'domain.local/OUname/SubOU/Users/first1 last1'
# Return the elements in $termednofwd that aren't also present in $termedfmr
Compare-Object $termednofwd $termedfmr -PassThru |
Where-Object SideIndicator -eq '<='
The above yields 'domain.local/OUname/SubOU/first2 last2', i.e. those element(s) in $termednofwd that aren't also present in $termedfmr.
Note: The above uses strings as input objects for brevity; in your case, since you're working with the objects returned by the Get-Mailbox cmdlet and want to compare based on their .Identity property values, you need to use:
# If you only need the identity values as results.
Compare-Object $termednofwd.Identity $termedfmr.Identity -PassThru |
Where-Object SideIndicator -eq '<='
# Alternatively, if you need the whole mailbox objects.
Compare-Object -Property Identity $termednofwd $termedfmr -PassThru |
Where-Object SideIndicator -eq '<='
See also: GitHub issue #4316, which proposes enhancing Compare-Object with set operations.
So this is essentially a practical example of Set theory i.e. I'd like all the items from Set 1 that isn't in Set 2. PowerShell doesn't have direct commandlets to do that but through the power of .Net you can lervage the Microsoft Linq libraries to do this work for you.
The only trick is that you need to cast your PowerShell variables to arrays of Objects to make the function call work right:
$results = [System.Linq.Enumerable]::Except([object[]]$mainArray, [object[]]$subArray)
Lastly, the items you are comparing in the two arrays have to be comparable. For simple things like strings this is always works. If the things you are comparing are more complex objects, they may not be comparable directly.

Undefined property name appears valid

The following two commands produce different output.
Get-ChildItem | Sort-Object -Property Length
Get-ChildItem | Sort-Object -Property Len
Len is not a member of System.IO.FileInfo. Is PowerShell matching Len to the Length member? If not, then why is there no error message saying that Len is not a property?
No, its not member of System.IO.FileInfo as you can see by adding the -Debugswitch:
Get-ChildItem | Sort-Object -Property Len -Debug
Output looks like:
DEBUG: "Sort-Object" - "Len" cannot be found in "InputObject".
I guess the reason for that is the defensive implementation of the cmdlet:
If an object does not have one of the specified properties, the
property value for that object is interpreted by the cmdlet as Null
and is placed at the end of the sort order.
To complement Martin Brandl's helpful answer with more general information:
While PowerShell's elastic syntax only applies to parameter names (e.g., specifying just -p for -Property) , not values (arguments), you do have options for completing values:
At edit time: use tab completion:
This works on the command line as well as in Visual Studio Code with the PowerShell extension installed (where you'll get IntelliSense as well), as long as PowerShell can statically infer the output type(s)[1]
of the command in the previous pipeline segment.
At runtime:
Sort-Object and several other cmdlets allow you to use a wildcard expression to match property names:
Get-ChildItem | Sort-Object -Property Len* # "Len*" matches "Length"
Note that multiple properties may match, and that a given parameter must be explicitly designed to support wildcards (unlike in POSIX-like shells, it is not PowerShell itself that resolves the wildcards).
When accessing a nonexistent property on an object directly, no error is reported by default, and $null is returned:
(Get-Item /).Foo # Outputs $null as the value of nonexistent property "Foo"
By contrast, if Set-StrictMode -Version 2 or higher is in effect, a (statement-terminating) error is reported in that case, but note that Set-StrictMode does not apply when passing property names as arguments, such as to Sort-Object above.
As for a possible motivation for why Sort-Object doesn't enforce the existence of specified properties:
PowerShell allows you to pass objects that are any mix of types as input through the pipeline, with the objects getting passed one at a time.
(Similarly, PowerShell's default array type is [object[]], which allows you to create mixed-type arrays such as 1, 'hi', $True)
Even with (potentially) homogeneous input (such as the [System.IO.FileInfo] instances emitted by Get-ChildItem -File, for instance), a receiving command cannot detect that case up front, because it only ever sees one object at a time.
In general, cmdlets should be able to handle a mix of types among the input gracefully, and treating nonexistent properties as $null is overall the better choice, especially given that:
a cmdlet may still be able to act meaningfully on the input if at least a subset of the input objects have the property of interest (see below).
a cmdlet cannot know in advance whether that subset is empty.
Example with heterogeneous input:
Send an array of custom objects through the pipeline and sort it by property val, which one of the objects lacks:
[pscustomobject] #{ n = 'o1'; val = 2 },
[pscustomobject] #{ n = 'o2' },
[pscustomobject] #{ n = 'o3'; val = 1 } | Sort-Object val
Output:
n val
- ---
o3 1
o1 2
o2
Sorting was performed among all the input objects that do have a .val property, whereas those that don't were placed at the end, as stated in the quote from Sort-Object's documentation in Martin's answer.
[1] This should be true of all built-in cmdlets; to ensure that it works with custom functions, define them with [OutputType(<type>)] attributes - see this answer of mine for more.

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)