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.
- 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
- 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
- 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.
- 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
- 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!