52

By default, any named function that has the [CmdletBinding()] attribute accepts the -debug and -verbose (and a few others) parameters and has the predefined $debug and $verbose variables. I'm trying to figure out how to pass them on to other cmdlet's that get called within the function.

Let's say I have a cmdlet like this:

function DoStuff() {
   [CmdletBinding()]

   PROCESS {
      new-item Test -type Directory
   }
}

If -debug or -verbose was passed into my function, I want to pass that flag into the new-item cmdlet. What's the right pattern for doing this?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Micah
  • 111,873
  • 86
  • 233
  • 325

9 Answers9

47

$PSBoundParameters isn't what you're looking for. The use of the [CmdletBinding()] attribute allows the usage of $PSCmdlet within your script, in addition to providing a Verbose flag. It is in fact this same Verbose that you're supposed to use.

Through [CmdletBinding()], you can access the bound parameters through $PSCmdlet.MyInvocation.BoundParameters. Here's a function that uses CmdletBinding and simply enters a nested prompt immediately in order examine the variables available inside the function scope.

PS D:\> function hi { [CmdletBinding()]param([string] $Salutation) $host.EnterNestedPrompt() }; hi -Salutation Yo -Verbose

PS D:\>>> $PSBoundParameters

____________________________________________________________________________________________________
PS D:\>>> $PSCmdlet.MyInvocation.BoundParameters

Key Value                                                                                                                                                                                                           
--- -----                                                                                                                                                                                                           
Salutation Yo                                                                                                                                                                                                              
Verbose   True                                                                                       

So in your example, you would want the following:

function DoStuff `
{
    [CmdletBinding()]
    param ()
    process
    {
      new-item Test -type Directory `
        -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    }
}

This covers -Verbose, -Verbose:$false, -Verbose:$true, and the case where the switch is not present at all.

bwerks
  • 8,651
  • 14
  • 68
  • 100
  • 1
    From what I understand, $PSBoundParameters is just a shortcut to $PSCmdlet.MyInvocation.BoundParameters. `Function test-function { [cmdletbinding()] Param(); Write-Host $PSBoundParameters; Write-Host $PSCmdlet.MyInvocation.BoundParameters; $PSCmdlet.MyInvocation.BoundParameters.Equals($PSBoundParameters) }` In your example where they show differently it is because you are entering a new execution context with it's own $PSBoundParameters when you go into the nested prompt. – New Guy Mar 12 '15 at 15:15
  • 1
    If you want to test for `-Verbose` in the command line *and* the `$VerbosePreference` value in your current shell, the shortest form is just to test for `-Verbose:($VerbosePreference -eq "Continue")`. Because, as @NewGuy explained, the `$VerbosePreference` value is always set appropriately. – alfred s. Nov 27 '20 at 12:10
38

Perhaps it sounds strange, but there isn't any easy way for a cmdlet to know its verbose or debug mode. Take a look at the related question:

How does a cmdlet know when it really should call WriteVerbose()?

One not perfect, but practically reasonable, option is to introduce your own cmdlet parameters (for example, $MyVerbose and $MyDebug) and use them in the code explicitly:

function DoStuff {
    [CmdletBinding()]
    param
    (
        # Unfortunately, we cannot use Verbose name with CmdletBinding
        [switch]$MyVerbose
    )

    process {

        if ($MyVerbose) {
            # Do verbose stuff
        }

        # Pass $MyVerbose in the cmdlet explicitly
        New-Item Test -Type Directory -Verbose:$MyVerbose
    }
}

DoStuff -MyVerbose

UPDATE

When we need only a switch (not, say, a verbosity level value) then the approach with $PSBoundParameters is perhaps better than proposed in the first part of this answer (with extra parameters):

function DoStuff {
    [CmdletBinding()]
    param()

    process {
        if ($PSBoundParameters['Verbose']) {
            # Do verbose stuff
        }

        New-Item Test -Type Directory -Verbose:($PSBoundParameters['Verbose'] -eq $true)
    }
}

DoStuff -Verbose

It's all not perfect anyway. If there are better solutions then I would really like to know them myself.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Roman Kuzmin
  • 40,627
  • 11
  • 95
  • 117
  • I think that second example might evaluate Verbose to true if it hasn't been explicitly set false when calling the function. It seems to assume true when given a null. – craika Nov 29 '10 at 12:20
  • @Alisdair Craik: good catch, thanks, I have corrected that (and tested this time). Weird, isn’t it? Note: in your answer there is perhaps a minor flaw, too: to check `ContainsKey()` is not enough, because the actual value behind that existing key can be still `$false`. It’s a rare case but it is not impossible. – Roman Kuzmin Nov 29 '10 at 12:51
  • 2
    That's the trick "-Vebose:(xxx)" I didn't know you could set a switch that way. I thought it was always include or exclude. I didn't know you could directly set the value of switch like that. – Micah Nov 29 '10 at 13:07
  • that's true - I've added another example now to take the value of the parameters. Cheers. – craika Nov 29 '10 at 13:18
  • 2
    @RomanKuzmin: there is a better solution. Since PowerShell already takes care of it the better solution is doing nothing. See http://stackoverflow.com/a/20830886/1242. For the detecting part see http://stackoverflow.com/a/20828118/1242 – Lars Truijens Dec 29 '13 at 22:41
  • @LarsTruijens is correct; except where PS modules are concerned... See https://developer42.wordpress.com/2017/02/04/powershell-suggestion-simplify-write-verbose-in-modules/ for info on the issue & suggested improvement. – JohnLBevan Feb 04 '17 at 11:03
  • 1
    @VasylZvarydchuk, this variant fails in the strict mode when `Verbose` is missing – Roman Kuzmin Dec 07 '18 at 10:34
29

There is no need. PowerShell already does this as the code below proves.

function f { [cmdletbinding()]Param()    
    "f is called"
    Write-Debug Debug
    Write-Verbose Verbose
}
function g { [cmdletbinding()]Param() 
    "g is called"
    f 
}
g -Debug -Verbose

The output is

g is called
f is called
DEBUG: Debug
VERBOSE: Verbose

It is not done as direct as passing -Debug to the next cmdlet though. It is done through the $DebugPreference and $VerbrosePreference variables. Write-Debug and Write-Verbose act like you would expect, but if you want to do something different with debug or verbose you can read here how to check for yourself.

Community
  • 1
  • 1
Lars Truijens
  • 42,837
  • 6
  • 126
  • 143
  • 5
    This works for Write-Verbose, as you say, however OP asked about causing New-Item to write verbose output, and this doesn't accomplish that. – bwerks Mar 30 '14 at 21:01
  • 4
    When you call a function with -Verbose, inside the function the $VerbosePrefence variable will be set to Continue (by default it is SilentlyContinue). This will in turn be passed on down to any functions called within the function. If those functions call Write-Verbose it will check the $VerbosePreference and print the verbose info. The problem with New-Item (and many of MS cmdlets) is that it will ignore this variable. So you either have to use $PSBoundParameters or look at the current setting of $VerbosePreference to determine whether you should manually specify -Verbose to New-Item. – New Guy Mar 12 '15 at 15:38
  • 1
    This should be higher on the list. – Law Sep 14 '16 at 16:48
  • 2
    While this example itself functions as advertised, it doesn't seem to do so anymore when f is moved into a module. Any idea what the reason for that is, and how to work around? – stijn May 03 '17 at 07:52
  • 3
    Answer for that question here: https://stackoverflow.com/questions/44900568/how-to-propagate-verbose-to-module-functions – stijn Jul 04 '17 at 14:17
11

Here's my solution:

function DoStuff {
    [CmdletBinding()]
    param ()

    BEGIN
    {
        $CMDOUT = @{
            Verbose = If ($PSBoundParameters.Verbose -eq $true) { $true } else { $false };
            Debug = If ($PSBoundParameters.Debug -eq $true) { $true } else { $false }
        }

    } # BEGIN ENDS

    PROCESS
    {
        New-Item Example -ItemType Directory @CMDOUT
    } # PROCESS ENDS

    END
    {

    } #END ENDS
}

What this does different from the other examples is that it will repsect "-Verbose:$false" or "-Debug:$false". It will only set -Verbose/-Debug to $true if you use the following:

DoStuff -Verbose
DoStuff -Verbose:$true
DoStuff -Debug
DoStuff -Debug:$true
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
desek
  • 111
  • 1
  • 3
4

The best way to do it is by setting the $VerbosePreference. This will enable the verbose level for the entire script. Do not forget to disable it by the end of the script.

Function test
{
    [CmdletBinding()]
    param($param1)

    if ($psBoundParameters['verbose'])
    {
        $VerbosePreference = "Continue"
        Write-Verbose " Verbose mode is on"
    }
    else
    {
        $VerbosePreference = "SilentlyContinue"
        Write-Verbose " Verbose mode is Off"
    }


    # <Your code>

}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
MilindK
  • 41
  • 1
  • I agree, by using this method you capture the verbose preference even in functions that have called other functions (which is correct as PowerShell passes it down by default), where as the other methods outlined only work if verbose has been explicitly called on each function. – Mark Wragg Mar 30 '17 at 11:19
3

You could build a new hash table based on the bound debug or verbose parameters and then splat it to the internal command. If you're just specifying switches (and aren't passing a false switch, like $debug:$false) you can just check for the existence of debug or verbose:

function DoStuff() { 
   [CmdletBinding()] 

   PROCESS { 
        $HT=@{Verbose=$PSBoundParameters.ContainsKey'Verbose');Debug=$PSBoundParameters.ContainsKey('Debug')}
      new-item Test -type Directory @HT
   } 
} 

If you want to pass the parameter value it's more complicated, but can be done with:

function DoStuff {  
   [CmdletBinding()]  
   param()
   PROCESS {  
   $v,$d = $null
   if(!$PSBoundParameters.TryGetValue('Verbose',[ref]$v)){$v=$false}
   if(!$PSBoundParameters.TryGetValue('Debug',[ref]$d)){$d=$false}
   $HT=@{Verbose=$v;Debug=$d} 
   new-item Test -type Directory @HT 
   }  
}  
craika
  • 1,062
  • 9
  • 9
1

You can set the VerbosePreference as a global variable on starting your script and then check for the global variable in your custom cmdlet.

Script:

$global:VerbosePreference = $VerbosePreference
Your-CmdLet

Your-CmdLet:

if ($global:VerbosePreference -eq 'Continue') {
   # verbose code
}

Checking explicitly for 'Continue' allows the script to be equal to -verbose:$false when you call the CmdLet from a script that doesn't set the global variable (in which case it's $null)

svandragt
  • 1,672
  • 20
  • 38
0

You do not have to do any checks or comparisons. Even though -Verbose (and -Debug) are of type [switch], they seem to understand not just $true and $false but also their preference variable. The preference variable also gets inherited correctly to all child functions that are called. I tried this on Powershell version 7.3.2 and it works as expected.

function Parent {
    [CmdletBinding()]param()
    Child
}

function Child {
    [CmdletBinding()]param()
    New-Item C:\TEST\SomeDir -Force -ItemType Directory -Verbose:$VerbosePreference -Debug:$DebugPreference
}

Parent -Verbose
Parent -Debug
David Trevor
  • 794
  • 1
  • 7
  • 22
-1

I think this is the easiest way:

Function Test {
    [CmdletBinding()]
    Param (
        [parameter(Mandatory=$False)]
        [String]$Message
    )

    Write-Host "This is INFO message"

    if ($PSBoundParameters.debug) {
        Write-Host -fore cyan "This is DEBUG message"
    }

    if ($PSBoundParameters.verbose) {
        Write-Host -fore green "This is VERBOSE message"
    }

    ""
}
Test -Verbose -Debug
emekm
  • 742
  • 5
  • 9
  • 2
    Downvote for the use of `Write-Host`, especially when you're using it to essentially replace the functionality provided by Write-Debug and Write-Verbose in a way that doesn't even do what those cmdlets do (write to their respective streams) but instead fakes it. – Dusty Vargas Mar 05 '20 at 22:38