#PSTip Taking control of verbose and debug output, part 2

Note: This tip requires PowerShell 3.0 or later.

In the first part of this series, we identified the problem–commands (scripts or functions) that ‘partially’ support verbose and debug messages. And the solution we identified was to make sure any command that you write is ‘advanced’ and supports common parameters (in a similar fashion that cmdlets support them). In this tip, we will try to narrow the scope of our test to commands that use verbose and/or debug output.

The idea is simple. If a command doesn’t use Write-Verbose and Write-Debug, it can remain ‘simple’ and will behave as expected anyway. There are a few ways to approach this. We can use regular expressions or PowerShell parser. Neither is optimal and should probably be used only if we are ‘stuck’ with PowerShell 2.0. In PowerShell 3.0 we can test command structure using its Abstract Syntax Tree (AST). AST has FindAll() method that will help us find any appearance of Write-Verbose/Write-Debug:

Get-Command -CommandType Function, ExternalScript | Where-Object {
    ($_.ScriptBlock.Ast.FindAll(
        {
            $args[0] -is [System.Management.Automation.Language.CommandAst] -and
            $args[0].CommandElements[0].Value -match '^Write-(Verbose|Debug)$'
        },
        $true
    )) -and
    -not ($_.CmdletBinding)
} 

CommandType     Name                                               Source
-----------     ----                                               ------
Function        Test-SimpleWithVerbose
Function        Test-Simple                                        TestVerbose
Function        Test-SimpleNested                                  TestVerbose
Function        Test-SimpleParam                                   TestVerbose
Function        Test-SimpleSubExpression                           TestVerbose
ExternalScript  FakeDebug.ps1                                      e:\PATH\FakeDebug.ps1

How does it work? The FindAll() method takes two arguments. The first argument is a script block that will be used to analyze any AST element present in command syntax tree. In this script block, $args[0] represents syntax element. We check if element is a command and if the command name is Write-Verbose or Write-Debug. The second argument is used to decide if search should be recursive or not. This method returns any element of AST for which script block returned $true. If we find Write-Verbose or Write-Debug then the second test will filter out any ‘advanced’ commands. The advantage of using AST is that it finds commands anywhere, including nested functions and sub-expressions inside double quotes:

function Test-SimpleSubExpression {
    "$(Write-Verbose test)"
}

function Test-SimpleNested {
    function Helper {
        Write-Verbose Helping
    }
    Helper
}

In my opinion though, there is no extra cost of making command ‘advanced’. Any function that is exported from a module is perfect candidate for ‘advanced’ function. However, if we want to fix commands without modifying the source, (e.g. 3rd-party module) being able to identify commands that require such update can limit amount of work. We will try to do that in the next parts of this series.

Share on: