Powershell write variable to stdout as running - powershell

In my powershell script I have a variable which captures its commands output
$files= dosomething
so running dosomething alone will produce output to the stdout. My question is how do I get that same functionality out of my variable as its running? mostly as a way to see progress.
maybe best to run the command and then somehow capture that to a variable?
UPDATE 1:
here's the command I'm trying to monitor:
$remotefiles = rclone md5sum remote: --dry-run --max-age "$lastrun" --filter-from "P:\scripts\filter-file.txt"
UPDATE 2:
running rclone md5sum remote: --dry-run --max-age "$lastrun" --filter-from "P:\scripts\filter-file.txt" directly produces output ...

The simplest solution is to enclose your assignment in (...), the grouping operator, which passes the result of the assignment through; e.g:
# Enclosing an assignment in (...) passes the value that is assigned through.
PS> ($date = Get-Date)
Friday, May 21, 2021 7:43:48 PM
Note:
Use of (...) implies that all command output is captured first, before passing it through; that is, you won't see the passed-through output until after the command has terminated - see the bottom section for a solution.
An assignment by default captures only success output (from PowerShell commands) / stdout output (from external commands).
If you (also) want to capture error-stream / stderr output, use 2>&1 to merge the error-stream / stderr output into the success stream / stdout (see the conceptual about_Redirection topic); in the case of the external rclone program (for brevity, the actual arguments are omitted and represented with placeholder ...):
# Merge stderr into stdout and capture the combination, and also pass it through.
([string[]] $remotefiles = rclone ... 2>&1)
Note: the [string[]] type constraint ensures that the stderr lines too are captured as strings; by default, PowerShell returns them as [System.Management.Automation.ErrorRecord] instances. If you want pipeline logic, i.e. to only capture a [string] scalar if there's only one output line and use an array only for multiple lines, use
($remotefiles = rclone ... 2>&1 | % ToString) instead.
As Abraham Zinala points out, PowerShell cmdlets (including advanced functions and scripts), support the common -OutVariable parameter, which also allows capturing a cmdlet's success output in addition to outputting it to the success output stream:
# Outputs *and* saves in variable $date.
# Note that the -OutVariable argument must *not* start with "$"
PS> Get-Date -OutVariable date
Friday, May 21, 2021 7:43:48 PM
Note: Using 2>&1 does not cause the -OutVariable to also capture error-stream output, but you can capture errors separately, using the common -ErrorVariable parameter.
Caveat: Surprisingly, the -OutVariable target variable[1] always receives a [System.Collections.ArrayList] instance, which is unusual in two respects:
PowerShell generally uses regular array ([object[]]) instances to collect multiple output objects, such as in an assignment.
While difference in (read-only) access between an array list and a regular array will rarely matter, the more problematic aspect is that an array list is used even when the cmdlet outputs just ONE object, unlike in assignments; in the case at hand this means:
$date = Get-Date stores a [datetime] instance in variable $date
whereas Get-Date -OutVariable date stores a single-element array list containing a [datetime] instance in $date.
This unexpected behavior is the subject of GitHub issue #3154.
By contrast, Tee-Object's -Variable parameter, as shown in SADIK KUZU's helpful answer, does not exhibit this unexpected behavior; that is, it behaves the same way as an assignment.
However, given that use of Tee-Object is by definition an additional call and therefore requires an extra pipeline segment, you do pay a performance penalty (though that may not matter in practice).
Solution for a long-running (external) command whose output should be passed through as it is being produced:
# Merge stderr into stdout and output in streaming fashion,
# while capturing the (stringified) lines in variable $remoteFiles
rclone ... 2>&1 | % ToString -OutVariable remoteFiles
Note: % is the built-in alias for the ForEach-Object cmdlet; ToString calls the .ToString() method on each object, which is necessary for converting the stderr output lines to strings, because PowerShell by default wraps them in[System.Management.Automation.ErrorRecord] instances; if conversion to strings isn't desired or necessary, replace
% ToString -OutVariable remoteFiles with
Tee-Object -Variable remoteFiles.
[1] The same applies to the other common -*Variable parameters, such as -ErrorVariable.

Tee-Object saves command output in a file or variable and also sends it down the pipeline.
Get-Process notepad | Tee-Object -Variable proc | Select-Object processname,handles
ProcessName Handles
----------- -------
notepad 43
notepad 37
notepad 38
notepad 38
This example gets a list of the processes running on the computer, saves them to the $proc variable, and pipes them to Select-Object.

Out-Host will send output to the console without sending it down the pipe
Function Test-Output {
param()
$list = #(
'Dogs',
'Cats',
'Monkeys'
)
$list | Out-Host
$list | Select-Object -First 1
}
$output = Test-Output
# When running the line above Dogs, Cats, and Monkeys will display in the console
# Dogs
# Cats
# Monkeys
# However the variable $output will only contain 'Dogs'
$output
# Dogs

Related

In a powershell pipeline, how to wait for a cmdlet to complete before proceding to the next one?

Consider the following function:
function myFunction {
100
sleep 1
200
sleep 1
300
sleep 1
}
As you can see it will emit those values, one by one down the pipeline.
But I want to wait for all the values to be emitted before going on. Like
myFunction | waitForThePreviousCommandToComplete | Format-Table
I want the Format-Table above to receive the entire array, instead of one-by-one items.
Is it even possible in Powershell?
Use
(...), the grouping operator in order to collect a command's output in full first, before sending it to the success output stream (the pipeline).
# Due to (...), doesn't send myfunction's output to Format-Table until it has run
# to completion and all its output has been collected.
(myFunction) | Format-Table
# Also works for entire pipelines.
(100, 200, 300 | ForEach-Object { $_; Start-Sleep 1 }) | Format-Table
Note:
If you need to up-front collect the output of multiple commands (pipelines) and / or language statements, use $(...), the subexpression operator instead, e.g. $(Get-Date -Year 2020; Get-Date -Year 2030) | Format-Table; the next point applies to it as well.
Whatever output was collected by (...) is enumerated, i.e., if the collected output is an enumerable, its elements are emitted one by one to the success output stream - albeit without any delay at that point.
Note that the collected output is invariably an enumerable (an array of type [object[]]) if two or more output objects were collected, but it also can be one in the usual event that a single object that itself is an enumerable was collected.
E.g., (Write-Output -NoEnumerate 1, 2, 3) | Measure-Object reports a count of 3, even though Write-Output -NoEnumerate output the given array as a single object (without (...), Measure-Object would report 1).
Typically, commands (cmdlets, functions, scripts) stream their output objects, i.e. emit them one by one to the pipeline, as soon as they are produced, while the command is still running, as your function does, and also act on their pipeline input one by one. However, some cmdlets invariably, themselves collect all input objects first, before they start emitting their output object(s), of conceptual necessity: notable examples are Sort-Object, Group-Object, and Measure-Object, all of which must act on the entirety of their input before they can start emitting results. Ditto for Format-Table when it is passed the -AutoSize switch, discussed next.
In the case of Format-Table, specifically, you can use the -AutoSize switch in order force it to collect all input first, in order to determine suitable display column widths based on all data (by default, Format-Table waits for 300 msecs. in order to determine column widths, based on whatever subset of the input data it has received by then).
However, this does not apply to so-called out-of-band-formatted objects, notably strings and primitive .NET types, which are still emitted (by their culture-invariant .ToString() representation) as they're being received.
Only complex objects (those with properties) are collected first, notably hashtables and [pscustomobject] instances; e.g.:
# Because this ForEach-Object call outputs complex objects (hashtables),
# Format-Table, due to -AutoSize, collects them all first,
# before producing its formatted output.
100, 200, 300 | ForEach-Object { #{ num = $_ }; Start-Sleep 1 } |
Format-Table -AutoSize
If you want to create a custom function that collects all of its pipeline input up front, you have two options:
Create a simple function that uses the automatic $input variable in its function body, which implicitly runs only after all input has been received; e.g.:
# This simple function simply relays its input, but
# implicitly only after all of it has been collected.
function waitForThePreviousCommandToComplete { $input }
# Output doesn't appear until after the ForEach-Object
# call has emitted all its output.
100, 200, 300 | ForEach-Object { $_; Start-Sleep 1 } | waitForThePreviousCommandToComplete
In the context of an advanced function, you'll have to manually collect all input, iteratively in the process block, via a list-type instance allocated in the begin block, which you can then process in the end block.
While using a simple function with $input is obviously simpler, you may still want an advanced one for all the additional benefits it offers (preventing unbound arguments, parameter validation, multiple pipeline-binding parameters, ...).
See this answer for an example.
Sort waits until it has everything.
myFunction | sort-object
Or:
(myFunction)
$(myfunction1; myFunction2)
myFunction | format-table -autosize
myFunction | more
See also: How to tell PowerShell to wait for each command to end before starting the next?
For some unknown reason, just putting the function inside brackets solved my problem:
(myFunction) | Format-Table

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)

Creating a File Changes What's Written To Host When Jobs Are Created [duplicate]

Using BorderAround emits "True" to the console.
$range = $sum_wksht.Range('B{0}:G{0}' -f ($crow))
$range.BorderAround(1, -4138)
This can be overcome by using one of the following.
$wasted = $range.BorderAround(1, -4138)
[void]$range.BorderAround(1, -4138)
Why is this needed? Am I not creating the range correctly? Is there a better workaround?
Why is this needed?
It is needed, because the BorderAround method has a return value and, in PowerShell, any command or expression that outputs (returns) data is implicitly output to the (success) output stream, which by default goes to the host, which is typically the console window (terminal) in which a PowerShell session runs.
That is, the data shows in the console/terminal, unless it is:
captured ($var = ...)
sent through the pipeline for further processing (... | ...; the last pipeline segment's command may or may not produce output itself)
redirected (... >)
or any combination thereof.
That is:
$range.BorderAround(1, -4138)
is (more efficient) shorthand for:
Write-Output $range.BorderAround(1, -4138)
(Explicit use of Write-Output is rarely needed.)
Since you don't want that output, you must suppress it, for which you have several options:
$null = ...
[void] (...)
... > $null
... | Out-Null
$null = ... may be the best overall choice, because:
It conveys the intent to suppress up front
While [void] = (...) does that too, it often requires you to enclose an expression in (...) for syntactic reasons; e.g., [void] 1 + 2 doesn't work as intended, only [void] (1 + 2); similarly, a command must always be enclosed in (...); [void] New-Item test.txt doesn't work, only [void] (New-Item test.txt) does.
It performs well with both command output (e.g., $null = Get-AdUser ...) and expression output (e.g., $null = $range.BorderAround(1, -4138)).
Conversely, avoid ... | Out-Null, because it is generally much slower (except in the edge case of a side effect-free expression's output in PowerShell (Core) 6+)[1].
However, if you need to silence all output streams - not just the success output, but also errors, verbose output, ... - you must use *> $null
Why does PowerShell produce output implicitly?
As a shell, PowerShell's output behavior is based on streams, as in traditional shells such as cmd.exe or Bash. (While traditional shells have 2 output streams - stdout and stderr - PowerShell has 6, so as to provide more sophisticated functionality - see about_Redirection.)
A cmdlet, script, or function can write to the output streams as often as it wants, and such output is usually instantly available for display but notably also to potential consumers, which enables the streaming, one-by-one processing that the pipeline provides.
This contrasts with traditional programming languages, whose output behavior is based on return values, typically provided via the return keyword, which conflates output data (the return value) with flow control (exit the scope and return to the caller).
A frequent pitfall is to expect PowerShell's return statement to act the same, but it doesn't: return <val> is just syntactic sugar for <val>; return, i.e., implicit output of <val> followed by an unconditional return of control to the caller; notably, the use of return does not preclude generation of output from earlier statements in the same scope.
Unlike traditional shells, PowerShell doesn't require an explicit write-to-the-output stream command in order to produce output:
While PowerShell does have a counterpart to echo, namely Write-Output, its use is rarely needed.
Among the rare cases where Write-Output is useful is preventing enumeration of a collection on output with -NoEnumerate, or to use common parameter -OutVariable to both output data and capture it in a variable (which is generally only needed for expressions, because cmdlets and advanced functions / scripts themselves support -OutVariable).
The implicit output behavior:
is generally a blessing:
for interactive experimentation - just type any statement - notably including expressions such as [IO.Path]::GetExtension('foo.txt') and [math]::Pow(2, 32) - and see its output (akin to the behavior of a REPL).
for writing concise code that doesn't need to spell out implied behavior (see example below).
can occasionally be a pitfall:
for users accustomed to the semantics of traditional programming languages.
due to the potential for accidental pollution of the output stream from statements that one doesn't expect to produce output, such as in your case; a more typical example is the .Add() method of the [System.Collections.ArrayList] class unexpectedly producing output.
Example:
# Define a function that takes an array of integers and
# outputs their hex representation (e.g., '0xa' for decimal 10)
function Get-HexNumber {
param([int[]] $numbers)
foreach ($i in $numbers) {
# Format the integer at hand
# *and implicitly output it*.
'0x{0}' -f $i.ToString('x')
}
}
# Call the function with integers 0 to 16 and loop over the
# results, sleeping 1 second between numbers.
Get-HexNumber (0..16) | ForEach-Object { "[$_]"; Start-Sleep 1 }
The above yields the following:
[0x0]
# 1-second pause
[0x1]
# 1-second pause
[0x2]
...
[0x10]
This demonstrates the streaming aspect of the behavior: Get-HexNumber's output is available to the ForEach-Object cmdlet call as it is being produced, not after Get-HexNumber has exited.
[1] In PowerShell (Core) 6+, Out-Null has an optimization if the only preceding pipeline segment is a side effect-free expression rather than a method or command call; e.g., 1..1e6 | Out-Null executes in almost no time, because the expression is seemingly not even executed. However, such a scenario is atypical, and the functionally equivalent Write-Output (1..1e6) | Out-Null takes a long time to run, much longer than $null = Write-Output (1..1e6).

PowerShell Log Function Readability

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.

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)