Getting the Verbose switch setting in PowerShell

Thursday, September 22, 2016

I've been struggling to find a reliable, portable way to find out if my PowerShell scripts were called with the -Verbose switch. The solutions I found so far are all fairly convoluted, and don't work well if the verbose switch is passed as -Verbose:$false or -Verbose:$true. This morning, it finally hit me.

But first let me explain what I needed.

Say I have a script called SomeScript.ps1. It starts like this:

[CmdletBinding()]
Param()

$Verbose = Get-Verbose

 If called like

.\SomeScript.ps1

the $Verbose variable should be $false; if called with the -Verbose switch, like this:

.\SomeScript.ps1 -Verbose

the $Verbose variable should be $true.

Sounds simple enough, doesn't it? It is, actually. The solution hinges on writing to the Verbose output stream and catching the output - without showing it. If the output is non-empty, we're in Verbose mode.

  1. Start by writing a dummy text to the Verbose output stream:
    Write-Verbose "TEST"
    This will either show up, or not, depending on the -Verbose setting

  2. Now catch the output into a variable:
    Write-Verbose "TEST" -OutVariable output
    Doesn't work: $output is always empty, since the text appears in the Verbose output and we only capture the standard output steam into $output

  3. So we need to redirect the Verbose output to the standard output stream:
    Write-Verbose "TEST" -OutVariable output 4>&1
    Close! Using the magical formula '4>&1' we tell PowerShell to merge everything written to the standard output stream (e.g. Write-Output) with everything written to the Verbose stream. Standard out0put has number 1, verbose output is number 4, and the ampersand means "merge".

    In Verbose mode, the output variable now contains a single line "TEST"; otherwise, it contains no lines. (The output variable turns out to be an ArrayList, by the way).

    Problem: in Verbose mode, the string "TEST" is also written to standard-output - as we requested.

  4. The final step, therefore, is to redirect the output of the whole thing to the trash, e.g. dump it to Out-Null:
    (Write-Verbose "TEST" -OutVariable output 4>&1) | Out-Null
  5. Great success! In Verbose mode, $output is "TEST", otherwise $output is empty. All we have to do is turn it into a Boolean for consumption by callers:
    return (!!$output)
    (Note: $output is an ArrayList, so if ($output) ... will test if it has any elements. Returning $output will return the arraylist itself, which is not what we want. (!$output) is the wrong way around, so (!!$output) is what we need.)

There you have it! We need to make sure, of course, that Get-Verbose.ps1 is itself a nice Cmdlet, to make it recognize the -Verbose switch. So we need a [CmdletBinding()], which makes the entire script:

<#
    Get-Verbose - determine if the Cmdlet flag -Verbose is set.
#>
[Cmdletbinding()]
Param()

# Write the string "TEST" to the verbose output stream,
# but redirect that to standard output using 4>&1.
# Discard the output using Out-Null, but capture it in a variable
# using -OutVariable (an ArrayList!)
(Write-Verbose "TEST" -OutVariable output 4>&1) | Out-Null

# If Verbose is $true, the output ArrayList will contain data;
# if not, it will be empty
return (!!$output)

 The script does require Powershell 3.0, because we need the &-operator to redirect output, which is "reserved for future use" in PowerShell 1.0 and 2.0. I guess this is "future use" ;-)

Update, May 22nd, 2018: The script can, of course, be turned into a one-liner:

$verbose = (!!(Write-Verbose "" 4>&1))

No script, no intermediate output variable!

Update, November 30th, 2018: Reader Gerard pointed out that you can also use

$PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent

I was under the impression that BoundParameters was a relatively recent addition to PowerShell and therefore less "portable", but I see mentions of it as far back as 2009, so that shouldn't be a concern - if it ever was. This solution does, however, require that your script is in fact a CmdLet, so it needs the CmdletBinding attribute. If not, there is no ["Verbose"] item in the BoundParameters collection and an error is thrown. But since your script really should have the CmdletBinding attribute anyway, it's a really good solution. And more the "Powershell way" than my workaround, which, by the way, also depends on CmdletBinding, but in a different way: it will always return $false, even if the script was called with -Verbose, only there is no error. Thanks, Gerard!