Powershell seems to tie select cmdlets to the default output stream, and not release it after a command finishes, affecting future command in unexpected ways. It is not consistent in how it does this, which makes generating predictable output impossible without explicitly directing output when writing the script.
This weirdness happens if you put the commands on separate lines, mix in different object generation cmdlets, etc. It doesn't happen if you run the commands on separate lines interactively, and it doesn't happen with objects automatially created on the command line, unless you mix in objects not automatically created on the command line.
I give command line interactive examples, but it happens if you put these commands into a script with each command on a spearate line and then run the script.
PS /home/dennis> get-date|select dayofweek ; get-date
DayOfWeek
---------
Monday
Monday
PS /home/dennis> "string1"|select length ; "string2"
Length
------
7
string2
And for fun, check out this one:
S /home/dennis> "string0" ;"string1"|select length ; get-host ;"string2" ;get-date; 567 ; get-host
string0
Length
------
7
1
string2
1
567
1
PS /home/dennis> cat test.ps1
"string0"
"string1"|select length
get-host
"string2"
get-date
567
(1..5)
get-host
PS /home/dennis> ./test.ps1
string0
Length
------
7
1
string2
1
567
1
2
3
4
5
1
...
This also affects objects which are not of the same type, and in fact, it affects objects which do not even have the properties in the select statement. Delaying is not an option, and explictly forcing the output with out-host or write-host will directly write to the powershell output device, making it useless to create a script that will be used to produce objects in a pipeline. It also messes up variables. Observe:
PS /home/dennis> $d = get-date | select dayofweek ; $e = get-date ; $d ; $e
DayOfWeek
---------
Monday
Monday
PS /home/dennis> $d
DayOfWeek
---------
Monday
PS /home/dennis> $e
Monday, August 5, 2019 12:33:47 PM
For those who are thinking, it is only a display issue, and the script can be written to display it correctly, I say again, this makes scripts useless as tools you can reuse in other scripts.
Observe how a pipeline inside a script affects commands in an independent interactive shell.
PS /home/dennis> cat test.ps1
"string0"
"string1"|select length
get-host
"string2"
get-date
567
get-host
PS /home/dennis> ./test.ps1|% {$_}
string0
Length
------
7
1
string2
1
567
1
PS /home/dennis> ./test.ps1|% {write-host $_}
string0
#{Length=7}
System.Management.Automation.Internal.Host.InternalHost
string2
8/5/19 12:50:54 PM
567
System.Management.Automation.Internal.Host.InternalHost
PS /home/dennis> ./test.ps1|% {$_|out-host}
string0
Length
------
7
Name : ConsoleHost
Version : 6.2.2
InstanceId : 4e46c643-1a9d-4c55-9151-b311f287a9cb
UI : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture : en-US
CurrentUICulture : en-US
PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
DebuggerEnabled : True
IsRunspacePushed : False
Runspace : System.Management.Automation.Runspaces.LocalRunspace
string2
Monday, August 5, 2019 1:20:24 PM
567
Name : ConsoleHost
Version : 6.2.2
InstanceId : 4e46c643-1a9d-4c55-9151-b311f287a9cb
UI : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture : en-US
CurrentUICulture : en-US
PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
DebuggerEnabled : True
IsRunspacePushed : False
Runspace : System.Management.Automation.Runspaces.LocalRunspace
In any shell script I expect that a command will execute independently of the previous command.
WhatTheFortran is the logic behind this behaviour? What is the official recommendation to avoid this unpredictability?
I know this is confusing. Any time select-object seems to output a table, format-table is actually running in the background. These format commands are kind of an illusion, and controlled by format files. But the object is still the same underneath.
$a = [pscustomobject]#{name='Joe'}
$b = [pscustomobject]#{address='here'}
$a,$b | format-table
name
----
Joe
$a,$b | format-list
name : Joe
address : here
Other workarounds:
$(get-date|select dayofweek ; get-date) | format-list
$("string1"|select length ; "string2") | format-list
$("string0" ;"string1"|select length ; get-host ;"string2" ;get-date; 567 ; get-host) | format-list
test.ps1 | format-list
The problem with the $d example, is the assignment to $d is only the first statement up to the first semicolon. This is a different issue.
$d = $(get-date | select dayofweek ; $e = get-date ; $d ; $e)
$d | format-list # 3 objects, unless you repeat the last line
An example like this works, because both commands output object types that have .format.ps1xml files. Once you use select-object, the output is a generic PSCustomObject. Actually, you can try putting get-date first in any script.
get-date;get-host
From Windows Powershell in Action:
Out-Default uses steppable pipelines to run the formatter
cmdlets [like format-table] to do its rendering and then
calls Out-Host to display the formatted output.
Related
For some reason, the object won't output until the sleep command is done.
[pscustomobject]#{message = 'hi'}; sleep 5
Here's another example. You won't see the output until the loop finishes.
foreach ($i in 1..60) {
if ($i -eq 1) { [pscustomobject]#{message = $i} }
sleep 1
}
I guess you have to output at least 2 objects to see anything? ¯\_(ツ)_/¯ After 15 seconds, you see both objects.
foreach ($i in 1..60) {
if ($i -eq 1 -or $i -eq 15) { [pscustomobject]#{message = $i} }
sleep 1
}
Or output enough properties (> 4) to implicitly call format-list instead of format-table. Format-table is the problem. This comes out right away.
[pscustomobject]#{a=1; b=2; c=3; d=4; e=5}; sleep 10
I wonder if a parameter to format-table could be added like -NoWait.
Known object types with format files containing column widths don't have this problem.
foreach ($i in 1..60) {
if ($i -eq 1) { get-process powershell }
sleep 1
}
Or objects that default to format-custom:
foreach ($i in 1..60) {
if ($i -eq 1) { get-date }
sleep 1
}
tl;dr
If a command's output results in automatic tabular display (implicit Format-Table), display output can situationally be delayed for up to 300 ms. (see below for why and when), which can have two unexpected effects:
As in the question, a subsequent Start-Sleep submitted before the delay has elapsed further delays output for (at least) the duration of the sleep - it effectively suspends completing the 300 ms. wait.
A subsequent Write-Host or Out-Host call can produce output that unexpectedly comes first.
You can force synchronous display output by piping the command to Out-Host or to Format-Table explicitly (or any of the other Format-* cmdlets).
However, doing so means producing for-display output only, which means you lose the ability to (meaningfully) capture or relay the command's output.
# The Out-Host forces instant display, before sleeping starts.
# However, use of Out-Host means you can't capture the output.
[pscustomobject] #{message = 'hi'} | Out-Host; sleep 5
The behavior is explained by the infamous PSv5+ asynchronous behavior of implicitly applied Format-Table output: For data types without predefined formatting data that have 4 or fewer properties (which is what auto-selects table display), it waits for up to 300 msecs. before displaying output, in an effort to determine suitable column widths.
If you use Start-Sleep before that period has elapsed, you suspend waiting for as long as you're sleeping.
Output objects that happen not to trigger implicit Format-Table formatting are not affected, however:
# Immediate output, before sleeping ends:
# Out-of-band formatting of a .NET primitive.
PS> 1; Start-Sleep 5
# Implicit Format-*List* formatting due to having 5+ properties.
PS> [pscustomobject]#{a=1; b=2; c=3; d=4; e=5}; sleep 10
By contrast, because your command's output is an object with just 1 property and its type ([pscustomobject]) has no predefined formatting data associated with it, it triggers implicit Format-Table formatting and therefore exhibits the problem.
In short: The following command outputs are affected, because they select implicit Format-Table output while lacking predefined column widths, necessitating the delay:
objects whose type happens to have 4 or fewer properties
if those types have no associated predefined formatting data (see about_Format.ps1xml), which is generally true for [pscustomobject] instances.
Additionally, but far less commonly, types with formatting data that default to table view but don't have column widths predefined, are also affected (e.g., the System.Guid type instances that New-Guid outputs).
Types without formatting data that have 5 or more properties default to implicitly applied Format-List, where, due to line-by-line output, there's no need to determine useful column widths, and therefore no delay.
Note that this is only a display problem, and that if the command is captured or sent to a pipeline the data is immediately output (though the command won't finish overall until the Start-Sleep period has elapsed):
# The ForEach-Object command's script block receives the [pscustomobject]
# instance right away (and itself prints it *immediately* to the display,
# due to outputting a *string* (which never triggers the asynchronous behavior).
& { [pscustomobject]#{message = 'hi'}; sleep 5 } | ForEach-Object { "[$_]" }
While there are several ways to force synchronous (immediate) display output, they all change the fundamental behavior of the command:
# Piping to Out-Host:
# Directly prints to the *display* (host).
# No way for a caller to capture the result or for processing
# the result in a pipeline.
[pscustomobject]#{message = 'hi'} | Out-Host; sleep 5
# Using Write-Host:
# Prints directly to the *display* (host) by default.
# While it *is* possible to capture the result via output stream 6.
# the information stream (6> file.txt), that output:
# * is invariably converted to *strings*
# * and the string representation does *not* use the friendly default
# output formatting; instead, the objects are stringified with simple
# [psobject.].ToString() calls, which results in a much less friendly
# representation.
Write-Host ([pscustomobject]#{message = 'hi'}); sleep 5
# Piping to a Format-* cmdlet explicitly:
# While this does write to the success-output stream (stream number 1),
# as the command would by default, what is written isn't the original
# objects, but *formatting instructions*, which are useless for further
# programmatic processing.
# However, for redirecting the output to a file with Out-File or >
# this makes no difference, because they convert the formatting instructions
# to the strings you would see on the screen by default.
# By contrast, using Set-Content or any other cmdlet that expects actual data
# would not work meaningfully.
[pscustomobject]#{message = 'hi'} | Format-Table; sleep 5
Pipe your custom object to the Out-Host cmdlet:
[pscustomobject]#{message = 'hi'} | Out-Host; sleep 5
When you use the Out-Host cmdlet, you are immediately displaying the results to the host. Without it, the object is output to the pipeline which is not returned until after the Start-Sleep cmdlet.
Output less than 5 properties, and format-table implicitly runs. Format-table will wait an indefinite amount of time for the second object, before displaying the first object. This is for object types (like pscustomobject) without an xml file that define a default table view.
# no output for 5 seconds
&{get-date
sleep 5
get-date} | format-table
DisplayHint DateTime Date Day DayOfWeek DayOfYear Hour Kind Millisecond Minute
----------- -------- ---- --- --------- --------- ---- ---- ----------- ------
DateTime Saturday, February 8, 2020 10:24:48 AM 2/8/2020 12:00:00 AM 8 Saturday 39 10 Local 618 24
DateTime Saturday, February 8, 2020 10:24:53 AM 2/8/2020 12:00:00 AM 8 Saturday 39 10 Local 892 24
Compare with format-list:
& {get-date
sleep 5
get-date} | format-list
DisplayHint : DateTime
Date : 2/8/2020 12:00:00 AM
Day : 8
DayOfWeek : Saturday
DayOfYear : 39
Hour : 20
Kind : Local
Millisecond : 408
Minute : 37
Month : 2
Second : 18
Ticks : 637167910384087860
TimeOfDay : 20:37:18.4087860
Year : 2020
DateTime : Saturday, February 8, 2020 8:37:18 PM
DisplayHint : DateTime
Date : 2/8/2020 12:00:00 AM
Day : 8
DayOfWeek : Saturday
DayOfYear : 39
Hour : 20
Kind : Local
Millisecond : 662
Minute : 37
Month : 2
Second : 23
Ticks : 637167910436622480
TimeOfDay : 20:37:23.6622480
Year : 2020
DateTime : Saturday, February 8, 2020 8:37:23 PM
For some reason, the object won't output until the sleep command is done.
[pscustomobject]#{message = 'hi'}; sleep 5
Here's another example. You won't see the output until the loop finishes.
foreach ($i in 1..60) {
if ($i -eq 1) { [pscustomobject]#{message = $i} }
sleep 1
}
I guess you have to output at least 2 objects to see anything? ¯\_(ツ)_/¯ After 15 seconds, you see both objects.
foreach ($i in 1..60) {
if ($i -eq 1 -or $i -eq 15) { [pscustomobject]#{message = $i} }
sleep 1
}
Or output enough properties (> 4) to implicitly call format-list instead of format-table. Format-table is the problem. This comes out right away.
[pscustomobject]#{a=1; b=2; c=3; d=4; e=5}; sleep 10
I wonder if a parameter to format-table could be added like -NoWait.
Known object types with format files containing column widths don't have this problem.
foreach ($i in 1..60) {
if ($i -eq 1) { get-process powershell }
sleep 1
}
Or objects that default to format-custom:
foreach ($i in 1..60) {
if ($i -eq 1) { get-date }
sleep 1
}
tl;dr
If a command's output results in automatic tabular display (implicit Format-Table), display output can situationally be delayed for up to 300 ms. (see below for why and when), which can have two unexpected effects:
As in the question, a subsequent Start-Sleep submitted before the delay has elapsed further delays output for (at least) the duration of the sleep - it effectively suspends completing the 300 ms. wait.
A subsequent Write-Host or Out-Host call can produce output that unexpectedly comes first.
You can force synchronous display output by piping the command to Out-Host or to Format-Table explicitly (or any of the other Format-* cmdlets).
However, doing so means producing for-display output only, which means you lose the ability to (meaningfully) capture or relay the command's output.
# The Out-Host forces instant display, before sleeping starts.
# However, use of Out-Host means you can't capture the output.
[pscustomobject] #{message = 'hi'} | Out-Host; sleep 5
The behavior is explained by the infamous PSv5+ asynchronous behavior of implicitly applied Format-Table output: For data types without predefined formatting data that have 4 or fewer properties (which is what auto-selects table display), it waits for up to 300 msecs. before displaying output, in an effort to determine suitable column widths.
If you use Start-Sleep before that period has elapsed, you suspend waiting for as long as you're sleeping.
Output objects that happen not to trigger implicit Format-Table formatting are not affected, however:
# Immediate output, before sleeping ends:
# Out-of-band formatting of a .NET primitive.
PS> 1; Start-Sleep 5
# Implicit Format-*List* formatting due to having 5+ properties.
PS> [pscustomobject]#{a=1; b=2; c=3; d=4; e=5}; sleep 10
By contrast, because your command's output is an object with just 1 property and its type ([pscustomobject]) has no predefined formatting data associated with it, it triggers implicit Format-Table formatting and therefore exhibits the problem.
In short: The following command outputs are affected, because they select implicit Format-Table output while lacking predefined column widths, necessitating the delay:
objects whose type happens to have 4 or fewer properties
if those types have no associated predefined formatting data (see about_Format.ps1xml), which is generally true for [pscustomobject] instances.
Additionally, but far less commonly, types with formatting data that default to table view but don't have column widths predefined, are also affected (e.g., the System.Guid type instances that New-Guid outputs).
Types without formatting data that have 5 or more properties default to implicitly applied Format-List, where, due to line-by-line output, there's no need to determine useful column widths, and therefore no delay.
Note that this is only a display problem, and that if the command is captured or sent to a pipeline the data is immediately output (though the command won't finish overall until the Start-Sleep period has elapsed):
# The ForEach-Object command's script block receives the [pscustomobject]
# instance right away (and itself prints it *immediately* to the display,
# due to outputting a *string* (which never triggers the asynchronous behavior).
& { [pscustomobject]#{message = 'hi'}; sleep 5 } | ForEach-Object { "[$_]" }
While there are several ways to force synchronous (immediate) display output, they all change the fundamental behavior of the command:
# Piping to Out-Host:
# Directly prints to the *display* (host).
# No way for a caller to capture the result or for processing
# the result in a pipeline.
[pscustomobject]#{message = 'hi'} | Out-Host; sleep 5
# Using Write-Host:
# Prints directly to the *display* (host) by default.
# While it *is* possible to capture the result via output stream 6.
# the information stream (6> file.txt), that output:
# * is invariably converted to *strings*
# * and the string representation does *not* use the friendly default
# output formatting; instead, the objects are stringified with simple
# [psobject.].ToString() calls, which results in a much less friendly
# representation.
Write-Host ([pscustomobject]#{message = 'hi'}); sleep 5
# Piping to a Format-* cmdlet explicitly:
# While this does write to the success-output stream (stream number 1),
# as the command would by default, what is written isn't the original
# objects, but *formatting instructions*, which are useless for further
# programmatic processing.
# However, for redirecting the output to a file with Out-File or >
# this makes no difference, because they convert the formatting instructions
# to the strings you would see on the screen by default.
# By contrast, using Set-Content or any other cmdlet that expects actual data
# would not work meaningfully.
[pscustomobject]#{message = 'hi'} | Format-Table; sleep 5
Pipe your custom object to the Out-Host cmdlet:
[pscustomobject]#{message = 'hi'} | Out-Host; sleep 5
When you use the Out-Host cmdlet, you are immediately displaying the results to the host. Without it, the object is output to the pipeline which is not returned until after the Start-Sleep cmdlet.
Output less than 5 properties, and format-table implicitly runs. Format-table will wait an indefinite amount of time for the second object, before displaying the first object. This is for object types (like pscustomobject) without an xml file that define a default table view.
# no output for 5 seconds
&{get-date
sleep 5
get-date} | format-table
DisplayHint DateTime Date Day DayOfWeek DayOfYear Hour Kind Millisecond Minute
----------- -------- ---- --- --------- --------- ---- ---- ----------- ------
DateTime Saturday, February 8, 2020 10:24:48 AM 2/8/2020 12:00:00 AM 8 Saturday 39 10 Local 618 24
DateTime Saturday, February 8, 2020 10:24:53 AM 2/8/2020 12:00:00 AM 8 Saturday 39 10 Local 892 24
Compare with format-list:
& {get-date
sleep 5
get-date} | format-list
DisplayHint : DateTime
Date : 2/8/2020 12:00:00 AM
Day : 8
DayOfWeek : Saturday
DayOfYear : 39
Hour : 20
Kind : Local
Millisecond : 408
Minute : 37
Month : 2
Second : 18
Ticks : 637167910384087860
TimeOfDay : 20:37:18.4087860
Year : 2020
DateTime : Saturday, February 8, 2020 8:37:18 PM
DisplayHint : DateTime
Date : 2/8/2020 12:00:00 AM
Day : 8
DayOfWeek : Saturday
DayOfYear : 39
Hour : 20
Kind : Local
Millisecond : 662
Minute : 37
Month : 2
Second : 23
Ticks : 637167910436622480
TimeOfDay : 20:37:23.6622480
Year : 2020
DateTime : Saturday, February 8, 2020 8:37:23 PM
I would like to add an asterisk to the beginning of liner in output:
$SYSLST1 = "OsName","OsVersion","TimeZone","CsName"
$X = (Get-ComputerInfo -Property $SYSLST1 | Format-List | Out-String).Trim()
$Y = ForEach ($Z in $X){
$Z.insert(0," * ")
}
Write-Host $Y
But it's only doing the first line:
* OsName : Microsoft Windows Server 2016 Standard
OsVersion : 10.0.14393
TimeZone : (UTC-05:00) Eastern Time (US & Canada)
CsName : SIS-2016-INT-ST
You need the -Stream switch to instruct Out-String to output individual lines - by default, you'll get a single, multi-line string.
Also, you can simplify your string-insertion task with the help of the -replace operator:
(
Get-ComputerInfo -Property $SYSLST1 | Format-List | Out-String -Stream
).Trim() -ne '' -replace '^', ' * '
-ne '' filters out the empty lines that result from calling .Trim() on all output lines (via member-access enumeration).
-replace '^', ' * ', replaces the start of the string (^) with the specified string, in effect inserting it at the beginning of each line.
Generally speaking, note that Format-* cmdlets output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system (which in your case are interpreted by Out-String) - see this answer.
In short: use Format-* cmdlets to format data for display, not for subsequent programmatic processing.
Elaborating on #mklement0's answer --
In my experience, PowerShell works best when you can defer formatting until the very end -- i.e. all processing should be done on objects, and then the final task is rendering strings.
Starting at the most basic script:
Get-ComputerInfo -Property $SYSLST1
OsName OsVersion TimeZone CsName
------ --------- -------- ------
Microsoft Windows Server 2019 Standard 10.0.17763 (UTC-08:00) Pacific Time (US & Canada) WIN-IJ0UD3SK4HR
The result is a single System.Management.Automation.PSCustomObject and by default, the object is run through Format-Table
A cool trick of PSCustomObject is that you can iterate over its properties:
$(Get-ComputerInfo -Property $SYSLST1).PSObject.Properties |
ForEach-Object { " * $($_.Name) : $($_.Value))" }
* OsName : Microsoft Windows Server 2019 Standard)
* OsVersion : 10.0.17763)
* TimeZone : (UTC-08:00) Pacific Time (US & Canada))
* CsName : WIN-IJ0UD3SK4HR)
This gives us full control over the string rendering and allows us to make tweaks to it without fear of breaking other parts of the script.
For some reason, the object won't output until the sleep command is done.
[pscustomobject]#{message = 'hi'}; sleep 5
Here's another example. You won't see the output until the loop finishes.
foreach ($i in 1..60) {
if ($i -eq 1) { [pscustomobject]#{message = $i} }
sleep 1
}
I guess you have to output at least 2 objects to see anything? ¯\_(ツ)_/¯ After 15 seconds, you see both objects.
foreach ($i in 1..60) {
if ($i -eq 1 -or $i -eq 15) { [pscustomobject]#{message = $i} }
sleep 1
}
Or output enough properties (> 4) to implicitly call format-list instead of format-table. Format-table is the problem. This comes out right away.
[pscustomobject]#{a=1; b=2; c=3; d=4; e=5}; sleep 10
I wonder if a parameter to format-table could be added like -NoWait.
Known object types with format files containing column widths don't have this problem.
foreach ($i in 1..60) {
if ($i -eq 1) { get-process powershell }
sleep 1
}
Or objects that default to format-custom:
foreach ($i in 1..60) {
if ($i -eq 1) { get-date }
sleep 1
}
tl;dr
If a command's output results in automatic tabular display (implicit Format-Table), display output can situationally be delayed for up to 300 ms. (see below for why and when), which can have two unexpected effects:
As in the question, a subsequent Start-Sleep submitted before the delay has elapsed further delays output for (at least) the duration of the sleep - it effectively suspends completing the 300 ms. wait.
A subsequent Write-Host or Out-Host call can produce output that unexpectedly comes first.
You can force synchronous display output by piping the command to Out-Host or to Format-Table explicitly (or any of the other Format-* cmdlets).
However, doing so means producing for-display output only, which means you lose the ability to (meaningfully) capture or relay the command's output.
# The Out-Host forces instant display, before sleeping starts.
# However, use of Out-Host means you can't capture the output.
[pscustomobject] #{message = 'hi'} | Out-Host; sleep 5
The behavior is explained by the infamous PSv5+ asynchronous behavior of implicitly applied Format-Table output: For data types without predefined formatting data that have 4 or fewer properties (which is what auto-selects table display), it waits for up to 300 msecs. before displaying output, in an effort to determine suitable column widths.
If you use Start-Sleep before that period has elapsed, you suspend waiting for as long as you're sleeping.
Output objects that happen not to trigger implicit Format-Table formatting are not affected, however:
# Immediate output, before sleeping ends:
# Out-of-band formatting of a .NET primitive.
PS> 1; Start-Sleep 5
# Implicit Format-*List* formatting due to having 5+ properties.
PS> [pscustomobject]#{a=1; b=2; c=3; d=4; e=5}; sleep 10
By contrast, because your command's output is an object with just 1 property and its type ([pscustomobject]) has no predefined formatting data associated with it, it triggers implicit Format-Table formatting and therefore exhibits the problem.
In short: The following command outputs are affected, because they select implicit Format-Table output while lacking predefined column widths, necessitating the delay:
objects whose type happens to have 4 or fewer properties
if those types have no associated predefined formatting data (see about_Format.ps1xml), which is generally true for [pscustomobject] instances.
Additionally, but far less commonly, types with formatting data that default to table view but don't have column widths predefined, are also affected (e.g., the System.Guid type instances that New-Guid outputs).
Types without formatting data that have 5 or more properties default to implicitly applied Format-List, where, due to line-by-line output, there's no need to determine useful column widths, and therefore no delay.
Note that this is only a display problem, and that if the command is captured or sent to a pipeline the data is immediately output (though the command won't finish overall until the Start-Sleep period has elapsed):
# The ForEach-Object command's script block receives the [pscustomobject]
# instance right away (and itself prints it *immediately* to the display,
# due to outputting a *string* (which never triggers the asynchronous behavior).
& { [pscustomobject]#{message = 'hi'}; sleep 5 } | ForEach-Object { "[$_]" }
While there are several ways to force synchronous (immediate) display output, they all change the fundamental behavior of the command:
# Piping to Out-Host:
# Directly prints to the *display* (host).
# No way for a caller to capture the result or for processing
# the result in a pipeline.
[pscustomobject]#{message = 'hi'} | Out-Host; sleep 5
# Using Write-Host:
# Prints directly to the *display* (host) by default.
# While it *is* possible to capture the result via output stream 6.
# the information stream (6> file.txt), that output:
# * is invariably converted to *strings*
# * and the string representation does *not* use the friendly default
# output formatting; instead, the objects are stringified with simple
# [psobject.].ToString() calls, which results in a much less friendly
# representation.
Write-Host ([pscustomobject]#{message = 'hi'}); sleep 5
# Piping to a Format-* cmdlet explicitly:
# While this does write to the success-output stream (stream number 1),
# as the command would by default, what is written isn't the original
# objects, but *formatting instructions*, which are useless for further
# programmatic processing.
# However, for redirecting the output to a file with Out-File or >
# this makes no difference, because they convert the formatting instructions
# to the strings you would see on the screen by default.
# By contrast, using Set-Content or any other cmdlet that expects actual data
# would not work meaningfully.
[pscustomobject]#{message = 'hi'} | Format-Table; sleep 5
Pipe your custom object to the Out-Host cmdlet:
[pscustomobject]#{message = 'hi'} | Out-Host; sleep 5
When you use the Out-Host cmdlet, you are immediately displaying the results to the host. Without it, the object is output to the pipeline which is not returned until after the Start-Sleep cmdlet.
Output less than 5 properties, and format-table implicitly runs. Format-table will wait an indefinite amount of time for the second object, before displaying the first object. This is for object types (like pscustomobject) without an xml file that define a default table view.
# no output for 5 seconds
&{get-date
sleep 5
get-date} | format-table
DisplayHint DateTime Date Day DayOfWeek DayOfYear Hour Kind Millisecond Minute
----------- -------- ---- --- --------- --------- ---- ---- ----------- ------
DateTime Saturday, February 8, 2020 10:24:48 AM 2/8/2020 12:00:00 AM 8 Saturday 39 10 Local 618 24
DateTime Saturday, February 8, 2020 10:24:53 AM 2/8/2020 12:00:00 AM 8 Saturday 39 10 Local 892 24
Compare with format-list:
& {get-date
sleep 5
get-date} | format-list
DisplayHint : DateTime
Date : 2/8/2020 12:00:00 AM
Day : 8
DayOfWeek : Saturday
DayOfYear : 39
Hour : 20
Kind : Local
Millisecond : 408
Minute : 37
Month : 2
Second : 18
Ticks : 637167910384087860
TimeOfDay : 20:37:18.4087860
Year : 2020
DateTime : Saturday, February 8, 2020 8:37:18 PM
DisplayHint : DateTime
Date : 2/8/2020 12:00:00 AM
Day : 8
DayOfWeek : Saturday
DayOfYear : 39
Hour : 20
Kind : Local
Millisecond : 662
Minute : 37
Month : 2
Second : 23
Ticks : 637167910436622480
TimeOfDay : 20:37:23.6622480
Year : 2020
DateTime : Saturday, February 8, 2020 8:37:23 PM
I'd like to pull a specific statistics from a _Total (the CounterSamples) into a variable, in order to further parse it.
This is what I've tried.
Trying to index with [1] gives error further below.
Looping didn't seem to get me very far either.
cls
$statToCollect = '\Process(_Total)\IO Data Operations/sec'
Get-Counter $statToCollect
Write-Host "================"
$saveStats = Get-Counter $statToCollect
$ctrSamples = $saveStats[1].CounterSamples
Write-Host "$ctrSamples"
Write-Host "$($saveStats)"
Write-Host "================"
$diskStats = Get-Counter $statToCollect
$diskStatsLoopCounter = 1
foreach ($diskStat in $diskStats)
{
if ($diskStatsLoopCounter -eq 1)
{
write-host "$($diskStat.CounterSamples)"
}
$diskStatsLoopCounter = $diskStatsLoopCounter + 1
}
Results:
Timestamp CounterSamples
--------- --------------
12/29/2014 9:27:49 AM \\mpcname\process(_total)\io data operations/sec :
970.6265098029
================
Unable to index into an object of type Microsoft.PowerShell.Commands.GetCounter.PerformanceCo
unterSampleSet.
At C:\Users\neal.walters\Documents\DiskUtil.ps1:6 char:26
+ $ctrSamples = $saveStats[ <<<< 1].CounterSamples
+ CategoryInfo : InvalidOperation: (1:Int32) [], RuntimeException
+ FullyQualifiedErrorId : CannotIndex
Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet
================
Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample
In your particular case $saveStats is only the one element.
PS C:\Users\mcameron> $saveStats.Count
1
Which is why this command would have returned null output as there is not a second.
PS C:\Users\mcameron> $saveStats[1]
Since there was only the one element either of the following options would have worked for this case.
PS C:\Users\mcameron> $saveStats[0]
PS C:\Users\mcameron> $saveStats
Also as for the line Write-Host "$($saveStats)" since $saveStats is an object and not a string it would not expand the way you expect. Pretty sure this occure because the ToString() is not overloaded to handle this so just the object type is outputted. Simply having $saveStats on its own would allow powershell to format it properly using its own built-in cmdlets.
PS C:\Users\mcameron> $saveStats
Timestamp CounterSamples
--------- --------------
12/29/2014 10:56:53 AM \\c3935\process(_total)\io data operations/sec :
27.7291444862573
Similar issue with the line write-host "$($diskStat.CounterSamples)" which has the same response as above.
As the other commenters and posters have said you most likely want one of the properties like CookedValue which can be easily converted to a string.
write-host "$($diskStat.CounterSamples.CookedValue)"
Using PowerShell version 4 on Windows 8.1:
Get-Counter returns a PerformanceCounterSampleSet and you can access the CounterSamples property to get an array of PerformanceCounterSample objects.
The particular property that you're interested in is CookedValue:
$statToCollect = '\Process(_Total)\IO Data Operations/sec'
$total = (Get-Counter $statToCollect).CounterSamples.CookedValue
This gets you the result as a double:
PS> $total
28.9450419770711
PS> $total.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Double System.ValueType