Friday Puzzle Solution: Why doesn’t Verbose work?

On Friday, February 17, 2017, I posted the following puzzle on Twitter and Facebook (and here!).

It shows a PowerShell function with a Write-Verbose command and asks “Why won’t this verbose string print?”

You might think of this as a beginner puzzle, but after years of using PowerShell, I still hit this issue occasionally, and I’ve seen functions in the PowerShell Gallery just like this one.

 

To prove we have a problem, let’s dot-source the file and run the function. Our first clue is that PowerShell is not tab-completing the -v, no matter how many times you bang on the TAB key. (Tell me you try only once!)

And, when you type the -Verbose, it has no effect.

What’s the problem?

The Solution: Simple functions don’t have common parameters

Verbose is one of the many, super-useful common parameters that Windows PowerShell adds to advanced functions. But, in its current incarnation, my Get-CultureDate function is a simple or standard PowerShell function, not an advanced function. Windows PowerShell adds common parameters only to advanced functions.

Get-Command -Syntax displays the function syntax. It confirms that our simple function does not have the PowerShell common parameters.

 

To convert my simple function into an advanced function, I have two options:

Add the CmdletBinding attribute

To convert a simple function to an advanced function, add the CmdletBinding attribute. It’s valid on any script or function. You can use its parameters, such as DefaultParameterSetName, HelpUri (supports online help), and SupportsShouldProcess (add WhatIf and Confirm parameters). But you can also add it without any CmdletBinding parameters. In that case, the parentheses that enclose its parameters is still required, but the parameters are not required.

function Get-CultureDate
{
    [CmdletBinding()]    # Add CmdletBinding attribute
    param
    (
        [System.DateTime]
        $Date = (Get-Date),
 
        [string[]]
        $Culture = 'en-US'
    )
...

To see the effect, use Get-Command -Syntax.

And, the Verbose parameter works.

 

Remember that when you add a CmdletBinding attribute to a function, you must add a Param block. It’s required with CmdletBinding, even when the function has no parameters.

If you omit the Param block, you get a syntax error. This error is not very helpful, but you can eliminate it by adding Param().

 

Add a Parameter attribute

The CmdletBinding attribute is sufficient for an advanced function, but it’s not required. The Parameter attribute works, too.

Typically, you add a Parameter attribute when you want to use one of the Parameter attribute parameters, like Mandatory or ValueFromPipeline. But, you can add a parameter attribute with no Parameter-attribute-parameters. Just use empty parentheses.

function Get-CultureDate
{
    param
    (
        [Parameter()]     # Add a parameter attribute
        [System.DateTime]
        $Date = (Get-Date),
 
        [string[]]
        $Culture
    )
...

When you add the Parameter attribute, Windows PowerShell adds the common parameters to your new advanced function.

In this case, we’ll make the Culture parameter mandatory.

function Get-CultureDate
{
    param
    (
        [System.DateTime]
        $Date = (Get-Date),
        
        [Parameter(Mandatory)]     # Add a parameter attribute
        [string[]]
        $Culture
    )
...

Get-Command -Syntax shows the effect.

And, now, the Verbose parameter works with this function.

Use the Write-Verbose -Verbose Parameter?

Another possible solution to this puzzle is to add the Verbose parameter to the Write-Verbose command in the script.

Write-Verbose -Message "Processing culture: $strCulture" -Verbose

The Verbose parameter doesn’t have any effect on a standard function that doesn’t have the parameter, but it certainly has an effect on the Write-Verbose cmdlet, which has the PowerShell common parameters.

Unfortunately, the effect is not what you might want for your user experience. Adding -Verbose in the script overrides the value of the user’s VerbosePreference setting and always displays the verbose message, even if the user doesn’t want it. In fact, the user can’t suppress it.

This is really a poor user experience, and I wouldn’t recommend it. Sometimes, even if something works, you shouldn’t do it.

It’s best to add the common parameters to your function and let the user determine whether and when they want to see verbose messages.

 

What about VerbosePreference?

Resetting the value of the VerbosePreference variable is another possible solution that I wouldn’t recommend.

In a complex language like PowerShell, it’s sometimes difficult to distinguish a bug from a feature. For me, that’s the case for the behavior of VerbosePreference. In practice, I never change the values of the preference variables in a script or module, because they affect all commands and modules in the session and, worst of all, they override the preference that the user sets for their session, often without warning. And, the user cannot turn it off.

If a user elects to change their VerbosePreference, that’s fine, but I script shouldn’t force a setting on the user.

Summary

In our puzzle, we tried to use a common parameter on a simple function that doesn’t have common parameters. But, it’s easy to convert a simple function to an advanced function. Just add the CmdletBinding attribute or the Parameter attribute.


Like this PowerShell puzzle? Our FridayPowerShellPuzzle repository includes all of the SAPIEN Technologies Friday PowerShell puzzles without the solutions, so you’re not even tempted to peek.

To read the solutions to our Friday PowerShell Puzzles, see Friday Puzzle Solutions in the SAPIEN blog.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. and a Microsoft Cloud and Datacenter MVP. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.