PowerShell Log Function Readability - powershell

Currently my log function spits out the information in a single column and is hard to read. Is there a way to make it split up into different columns which each (DisplayName, PoolName, PoolSnapshot, and DesktopSVIVmSnapshot) and its respective information is put correctly?
function log ([string]$entry) {
Write-Output $entry | Out-File -Append "C:\logs\SNAPSHOT.csv"
}
Add-PSSnapin Quest.ActiveRoles.ADManagement
$date = Get-Date -Format "MM-dd-yyyy"
$time = Get-Date -Format "hh:mm:sstt"
# begin log
log $(Get-Date)
log "The below Desktops are not using the correct Snapshot."
if (#($DesktopExceptions).Count -lt 1) {
Write-Output "All desktops in $pool are currently using the correct snapshots." |
Out-File -Append "C:\logs\SNAPSHOT.csv"
} else {
Write-Output $DesktopExceptions |
Select-Object DisplayName,PoolName,PoolSnapshot,DesktopSVIVmSnapshot |
sort DisplayName |
Out-File -Append "C:\logs\SNAPSHOT.csv"
}
log $(Get-Date)
09/11/2017 12:16:17
DisplayName PoolName PoolSnapshot DesktopSVIVmSnapshot
----------- -------- ------------ --------------------
xxxc-13v xxxc-xxx /8-11-2017/09-07-2017 /8-11-2017
xxxc-15v xxxc-xxx /8-11-2017/09-07-2017 /8-11-2017
xxxc-1v xxxc-xxx /8-11-2017/09-07-2017 /8-11-2017
xxxc-20v xxxc-xxx /8-11-2017/09-07-2017 /8-11-2017
Note: I removed parts of the log for in the hopes to not make the post long.

CSV files require uniform lines: a header line with column names, followed by data lines containing column values.
By writing the output from Get-Date first - a single date/time string - followed by another single-string output, followed by multi-column output from your $DesktopExceptions | Select-Object ... call, you're by definition not creating a valid CSV file.
If you still want to create such a file:
log (Get-Date) # With a single command, you don't need $(...) - (...) will do.
log "The below Desktops are not using the correct Snapshot."
If ($DesktopExceptions) # a non-empty array / non-$null object
{
log ($DesktopExceptions |
Select-Object DisplayName,PoolName,PoolSnapshot,DesktopSVIVmSnapshot |
Sort-Object DisplayName |
ConvertTo-Csv -NoTypeInformation)
}
Else
{
log "All desktops in $pool are currently using the correct snapshots."
}
log (Get-Date)
By defining your log() function's parameter as type [string], you're effectively forcing stringification of whatever object you pass to it. This stringification is the same you get when you embed a variable reference or command inside "..." (string expansion / interpolation) - but it is not the same as what you get by default, when you print to the console.
Out-File, by contrast, does result in the same output you get when printing to the console, which, however, is a format for human consumption, not for machine parsing (as CSV is, for instance).
To get CSV-formatted output, you must either use Export-Csv - to write directly to a file - or ConvertTo-Csv- to get a string representation.
Also note that there's typically no reason to use Write-Output explicitly - any command / expression's output that is not explicitly assigned to a variable / redirected (to a file or $null) is implicitly sent to PowerShell's [success] output stream; e.g., Write-Output Get-Date is the same as just Get-Date.

It looks like you're just writing an object, and taking the default PowerShell formatter behavior.
A better thing to do is make your log only responsible for one thing - writing messages to a file (no formatting). Here's an example of what you might try:
function Write-LogMessage {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, HelpMessage = "The text-content to write to the log file.",
ValueFromPipeline = $true)]
[string]$Text
)
Process {
Write-Host -ForegroundColor Green $Text
}
}
Set-Alias log Write-LogMessage
Note: This example writes directly to the PowerShell console, but you would in practice need to direct output to a file (using Out-File or one of the redirection operators - see Get-Help about_Operators).
To use it, you would write something like this:
"This is a message that would be written" | Write-LogMessage
For your specific example, you could just format the message inline, and pipe it:
Write-Output $DesktopExceptions | Select-Object DisplayName,PoolName,PoolSnapshot,DesktopSVIVmSnapshot | sort DisplayName | ForEach-Object { "{0}: Host = {1}, Pool = {2}, Pool SN = {3}, SVIV Snapshot = {4}" -f (Get-Date), $_.DisplayName, $_.PoolName, $_.PoolSnapshot, $_.DesktopSVIVmSnapshot } | log
Note that you don't need the log statement: just add formatting before piping to the Out-File cmdlet, and you'll get what you're after.
Edit: The OP asked in the original post how to format columns (tabular output). To achieve this, you can use either the ConvertTo-Csv or Export-Csv cmdlets (generally, you would use the -NoTypeInformation switch parameter with these commands, to avoid the first line of the output being a type definition). An example of this is:
$DesktopExceptions | Select-Object DisplayName,PoolName,PoolSnapshot,DesktopSVIVmSnapshot | sort DisplayName | Export-Csv C:\Temp\Datum.csv -NoTypeInformation
As pointed out in another answer, using Write-Output is not required, because PowerShell automatically writes all output to the output stream unless otherwise directed (using file redirection, a redirection operator, or the Out-Null cmdlet).

Please read my answer as part solution and part advice.
The "problem" with PowerShell is that it doesn't capture only the output of your code. It will capture output from other scripts, modules and executables. In other words, any attempt to make logging behave like it's generated by e.g. C# with NLOG, has an inherent problem.
I looked into this subject myself for a complex continuous delivery pipeline I'm building. I understood that a structured log will not be 100% possible and therefore I accepted the purpose of PowerShell transcription (Start-Transcript). But still I wanted to avoid creating functions like Write-Log and if possible provide an enhanced output for all code that uses Write-Debug, Write-Verbose functionality.
I ended up creating XWrite PowerShell module which works very well, even to my own suprize. I use it because it enhances the produced trace message by the caller's name (cmdlet or script) and a timestamp. The caller's name helps a lot with troubleshooting and the timestamp I use to implicitly benchmark. here are a couple of example
DEBUG: Test-MyXWrite.ps1: Hello
DEBUG: Script: Test-MyXWrite.ps1: 20170804: 10:57:27.845: Hello
There are some limitations though. Any binary's code trace output will not be enhanced. Also if a cmllet refers explicitly to the Write-* using their full namespace it will not work. To capture line by line all trace and output requires some very deep into the .net types of PowerShell implementation hooking. There is a guy who has done this, but I don't want to get influence the PowerShell process's behavior that aggresively. And at this moment I believe that to be the role of the transcription.
If you like the idea, install the module from XWrite
At some point, I would like to extend the module with a redirection to telemetry services, but I've still not decided I want to do that, because I will not capture the above mentioned exceptions and other executable. It will just offer me visible progress as the script is executing.

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)

Again with Powershell: I know how to create a file but I don't know how to write in it [duplicate]

I am creating a script and want to both use Write-Host and Write-Output
As I work I want a backup of information I pull from AD to also become attached to a .txt file.
This is more of a backup in case I miss a piece of information and need to go back and recreate a ticket. Anyways I have a sample of my script, form what I can tell it should be working. If someone with a bit more experience can take a look or point me in the right direction I would appreciate it. If I need to add any more of the script I can provide this. Thanks in Advance.
Import-Module activedirectory
$object = Get-ADUser $sid -Properties * | Select-Object EmailAddress
Write-Host Email: $object.EmailAddress
Write-Output ("Email: $object.EmailAddress") >> C:\psoutput\psoutput.txt -Append
This will create the .txt file of course but is also add other information such as:
Email: #{GivenName=myfirstname; Surname=mylastname; SamAccountName=myid; DisplayName=lastname, firstname - Contingent Worker; City=; EmailAddress=myemailaddress#mywork.com; EmployeeID=; Enabled=True; OfficePhone=; MobilePhone=(555) 555-5555; LockedOut=False; LockOutTime=0; AccountExpirationDate=05/09/2020 00:00:00; PasswordExpired=False; PasswordLastSet=12/03/2019 12:16:37}.EmailAddress
-Append
I am looking to have the output like the following...
name: username
email: user email address
phone: user phone number
etc...
All general information from Active Directory
Thanks again for the suggestions
Don't use write-output. Use (Get-ADUser $sid -properties mail).mail.
Like this:
Add-Content -Path "FilePath" -Value "Email: $((Get-ADUser $sid -properties mail).mail)"
Write-Output ("Email: $object.EmailAddress")
As an aside: No need for (...) here.
This doesn't do what you expect it to: it stringifies $object as a whole and then appends .EmailAddress verbatim; in order to embed an expression, such as accessing a property inside "..." (an expandable string), you need $(), the subexpression operator.
Write-Output "Email: $($object.EmailAddress)" >> C:\psoutput\psoutput.txt
See this answer for an overview of the syntax in PowerShell expandable strings.
Or, more simply, using PowerShell's implicit output behavior (use of Write-Output is rarely necessary):
"Email: $($object.EmailAddress)" >> C:\psoutput\psoutput.txt
>> C:\psoutput\psoutput.txt -Append
>> is effectively an alias for Out-File -Append (just like > is for just Out-File), so not only is there no need for -Append, it isn't interpreted by >>, which accepts only the filename operand.
Instead, -Append was interpreted by Write-Output, which is why it ended up literally in your output file.
Perhaps surprisingly, while a redirection such as >> C:\psoutput\psoutput.txt is typically placed last on the command line, that is not a syntactic requirement: other arguments may follow.
I am looking to have the output like the following..
It sounds like you want formatting as provided by the Format-List cmdlet:
$object | Format-List >> C:\psoutput\psoutput.txt
Note that > / >> / Out-File apply the default string formatting, i.e. the same representation that would by default display in the console.
By using an explicit Format-* cmdlet, you can control that formatting, but note two things about Out-File in general:
As you're outputting for-display formats, the resulting file may not be suitable for further programmatic processing.
To prevent truncation of values, you may have to pass a -Width argument to Out-File, control the enumeration length of nested properties with $FormatEnumerationLimit, and, in the case of Format-Table, specify -AutoSize.
You don't really need to use Write-Output at all. Try this to just get your string to your file:
("Email: " + $object.EmailAddress) >> C:\psoutput\psoutput.txt
You don't need to specify append because '>>' already does that for you

Trying to test code, keep getting "System.____comobject" instead of variable value

I'm trying to write a small powershell script that does a few things
1) Parses Inbox items in my outlook
2) Searches for a RegEx string
3) Dumps the line that matches the RegEx string into a CSV
I can't get #3 to work. It definitely runs for about 10 minutes, but the resulting csv is empty.
Here's a snippet of what I want it to look for:
Account Name: Jbond
I tried just slapping a "Write-Host $variable" in various parts to see what was happening, but all I get is "System.____comobject". I can't find a solution online to just convert this into plain text.
Add-Type -Assembly "Microsoft.Office.Interop.Outlook"
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
$RE = [RegEx]'(?sm)Account Name\s*:\s*(?<AccName>.*?)$.*'
$Data = ForEach ($item in $inbox.items){
$resultText = $item.item.Value
Write-Host $resultText
if ($item.from -like "email#email.org"){
if ($item.body -match $RE){
[PSCustomObject]#{
AccName = $Matches.AccName
}
}
}
}
$Data
$Data | Export-CSv '.\data.csv' -NoTypeInformation
tl;dr:
Use:
$variable | Out-Host # or: Out-Host InputObject $variable
rather than
Write-Host $variable
to get meaningful output formatting.
Background information and debugging tips below.
Try a combination of the following approaches:
Use interactive debugging:
Use GUI editor Visual Studio Code with the PowerShell extension to place breakpoints in your code and inspect variable values interactively. (In Windows PowerShell you can also use the ISE, but it is obsolescent.)
Less conveniently, use the *-PSBreakpoint cmdlets to manage breakpoints that are hit when you run your script in a console (terminal) window. A simple alternative is to add Wait-Debugger statements to your script, which, when hit, break unconditionally.
Produce helpful debugging output:
Generally, use Write-Debug rather than Write-Host, which has two advantages:
You can leave Write-Debug calls in your code for on-demand debugging at any time:
They are silent by default, and only produce output on an opt-in basis, via (temporarily) setting $DebugPreference = 'Continue' beforehand or passing the -Debug switch (if your script / function is an advanced one, though note that in Windows PowerShell this will present a prompt whenever a Write-Debug call is hit).
You do, however, pay a performance penalty for leaving Write-Debug calls in your code.
Debug output is clearly marked as such, colored and prefixed with DEBUG:.
The problem you experienced with Write-Host is that all Write-* cmdlets perform simple .ToString() stringification of their arguments, which often results in unhelpful representations, such as System.____comobject in your case.
To get the same rich output formatting you would get in the console, use the following technique, which uses Out-String as a helper command:
$variable | Out-String | Write-Debug
If you want to control the view (list vs. table vs. wide vs. custom) explicitly, insert a Format-* call; e.g.:
$variable | Format-List | Out-String | Write-Debug
It is generally only the standard Out-* cmdlets that use PowerShell's output formatting system.
A quick-and-dirty alternative to Write-Debug is to use Out-Host rather than Write-Host - e.g., for quick insertion of debugging commands that you'll remove later; Out-Host itself performs the usual output formatting, which simplifies matters:
# Default for-display formatting
$variable | Out-Host # or: Out-Host -InputObject $variable
# Explicit formatting
$variable | Format-List | Out-Host
Caveat: Aside from formatting, another important difference between Write-Host and Out-Host is that in PSv5+ only Write-Host writes to the host via the information stream (stream number 6), whereas Out-Host truly writes directly to the host, which means that its output cannot be captured with redirections such as 6> or *> - see about_Redirection.

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)

Powershell - avoid repeating arguments of Out-File

Is there any way to avoid passing parameters to a function, like "-Append $outfile" to Out-File, every time? I have a script which collects data from the system, something like:
... collect OS information ... | Out-File -Append $output
... collect local users ... | Out-File -Append $output
... collect logfile permissions ... | Out-File -Append $output
etc.
The last command in the pipe is most of the time Out-File -Append $output - can this be done more elegant? I had different ideas:
Create a wrapper function which passes the needed parameters to Out-File command - already tried, but I had problems to make it pipe-compatible
Write all output into a String-Variable and write the content at the end of all commands into the file - needs a lot of memory
Create something like an Output-Writer-Object which only receives once at initialization the necessary paramters - not tried yet
Thank you very much for your help!
You dont appear to be using a lot of arguments for this to be incredibly useful but a good suggestion would be to use splatting. I added some more parameters to illustrate how clean it can make code appear while still being functional.
$options = #{
Append = $True
FilePath = $output
Encoding = "Unicode"
Width = 400
}
Build a hastable of options and splat the cmdlet with them
... collect OS information ... | Out-File #options
... collect local users ... | Out-File #options
... collect logfile permissions ... | Out-File #options
Outside of that a wrapper function (of filter if it is easier) like you suggest would be another option. Look at the options in this answer. Specifically the filter
You want to use the $PSDefaultParameterValues preference variable. Something like this:
$PSDefaultParameterValues = #{
"Out-File:Encoding"="utf8";
"Out-File:Append"=$true;
"Out-File:FilePath"=$output
}
This feature is especially useful when you must specify the same alternate parameter value nearly every time you use the command or when a particular parameter value is difficult to remember, such as an email server name or project GUID.
Or put everything inside a function or scriptblock. Note that out-file defaults to utf16 encoding, and can mix encodings, as opposed to add-content.
& {
... collect OS information ...
... collect local users ...
... collect logfile permissions ...
} | add-content $output