PowerShell switch parameter doesn't work as expected

572 views Asked by At

I have the following PowerShell function to help me do benchmarks. The idea is that you provide the command and a number and the function will run the code that number of times. After that the function will report the testing result, such as Min, Max and Average time taken.

function Measure-MyCommand()
{
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $True)] [scriptblock] $ScriptBlock,
        [Parameter()] [int] $Count = 1,
        [Parameter()] [switch] $ShowOutput
    )

    $time_elapsed = @();
    while($Count -ge 1) {
        $timer = New-Object 'Diagnostics.Stopwatch';
        $timer.Start();
        $temp = & $ScriptBlock;
        if($ShowOutput) {
            Write-Output $temp;
        }
        $timer.Stop();
        $time_elapsed += $timer.Elapsed;
        $Count--;
    }

    $stats = $time_elapsed | Measure-Object -Average -Minimum -Maximum -Property Ticks;

    Write-Host "Min: $((New-Object 'System.TimeSpan' $stats.Minimum).TotalMilliseconds) ms";
    Write-Host "Max: $((New-Object 'System.TimeSpan' $stats.Maximum).TotalMilliseconds) ms";
    Write-Host "Avg: $((New-Object 'System.TimeSpan' $stats.Average).TotalMilliseconds) ms";
}

The problem is with the switch parameter $ShowOutput. As I understand, when you provide a switch parameter, its value is true. Otherwise it's false. However it doesn't seem to work. See my testing.

PS C:\> Measure-MyCommand -ScriptBlock {Write-Host "f"} -Count 3 -ShowOutput
f
f
f
Min: 0.4935 ms
Max: 0.8392 ms
Avg: 0.6115 ms
PS C:\> Measure-MyCommand -ScriptBlock {Write-Host "f"} -Count 3
f
f
f
Min: 0.4955 ms
Max: 0.8296 ms
Avg: 0.6251 ms
PS C:\>

Can anyone help to explain it please?

2

There are 2 answers

0
Crypt32 On BEST ANSWER

This is because Write-Host doesn't return the object and is not a part of pipeline stream, instead it sends text directly to console. You can't simply hide Write-Host output by writing it to variable, because Write-Host writes to console stream which isn't exposed in PowerShell before version 5. What you need is to replace Write-Host with Write-Output cmdlet call. Then you get what you expect. This solution is valid for PowerShell 2.0+.

Update:

Starting with PowerShell 5, Write-Host writes to its dedicated stream and you can handle and redirect this stream somewhere else. See this response for more details: https://stackoverflow.com/a/60353648/3997611.

0
mklement0 On

The problem is with the specific script block you're passing, not with your [switch] parameter declaration (which works fine):

{Write-Host "f"}

By using Write-Host, you're bypassing PowerShell's success output stream (which the assignment to variable $temp collects) and printing directly to the host[1] (the console), so your output always prints.

To print to the success output stream, use Write-Output, or better yet, use PowerShell's implicit output feature:

# "f", due not being capture or redirected, is implicitly sent 
# to the success output stream. 
Measure-MyCommand -ScriptBlock { "f" } -Count 3 -ShowOutput

[1] In PowerShell 5.0 and higher, Write-Host now writes to a new stream, the information stream (number 6), which by default prints to the host. See about_Redirection. Therefore, a 6> redirection now does allow you to capture Write-Host output; e.g.: $temp = Write-Host hi 6>&1. Note that the type of the objects captured this way is System.Management.Automation.InformationRecord.