17

I have a PowerShell module that encapsulates a number of commonly-used business functions. It's not generally called from the console; rather, its functions are called by automated deployment and management scripts that import the module.

The module incorporates a logging function that writes to both a centralised logging location. I'd also like to hook into the Write-Verbose functionality to write to the console as well.

#'Start Script.ps1
#'----------------

Import-Module Corporate
Write-Logger 'Foo'

My restriction is that - from within the Corporate PowerShell module - I need to determine whether Script.ps1 has been called with the -Verbose parameter. Ideally, I would like the determination code entirely within the module itself.

Here's an example:

[CmdletBinding()]
Param ()

New-Module -Name TempModule -ScriptBlock {
    function Test-ModuleVerbose() {
        [CmdletBinding()]
        Param ()

        PROCESS {
            $vb = ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -eq $true)
            Write-Host ("1: Module verbose preference: " + ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -eq $true))
            Write-Host ("2: Module verbose preference: " + $Script:VerbosePreference)
            Write-Host ("3: Module verbose preference: " + $VerbosePreference)
        }
    }
} | Out-Null

function Test-Verbose() {
    [CmdletBinding()]
    Param ()

    PROCESS {
        Write-Host ("Verbose preference: $VerbosePreference")
        Test-ModuleVerbose
    }
}

Test-Verbose

Save the above as test.ps1. When invoked from the console:

PS C:\temp> .\test.ps1
Verbose preference: SilentlyContinue
1: Module verbose preference: False
2: Module verbose preference:
3: Module verbose preference: SilentlyContinue

PS C:\temp> .\test.ps1 -Verbose
VERBOSE: Exporting function 'Test-ModuleVerbose'.
VERBOSE: Importing function 'Test-ModuleVerbose'.
Verbose preference: Continue
1: Module verbose preference: False
2: Module verbose preference:
3: Module verbose preference: SilentlyContinue

As you can see, the $VerbosePreference variable is not available from within the module. Is there a way of picking up from within the module whether the calling script has been invoked with the -Verbose flag?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dan
  • 385
  • 1
  • 2
  • 11
  • You can declare switch parameter for the script and then depending on it's value, set some global variable. Also you can set/unset environment variable and check it's value in the cmdlet. –  Jan 07 '14 at 06:41

4 Answers4

17

It is possible to pass-through most of the common parameters using the matching preference variables and a syntax like this, -Parameter:$ParameterPreference. So, for the specific case of verbose, the syntax is -Verbose:$VerbosePreference.

There are a couple of exceptions:

  • Debug : the value of $DebugPreference is automatically passed-through, but specifying the -Debug switch forces $DebugPreference to Inquire.
  • WhatIf : automatically passed-through.

I have modified the OP code sample as follows:

[CmdletBinding(SupportsShouldProcess=$true)]
param(
    [Switch]$FullPassThru
)

New-Module -Name TempModule -ScriptBlock {
        function Test-ModuleVerbose
        {
            [CmdletBinding(SupportsShouldProcess=$true)]
            param ()

            Write-Host "1: Module: verbose parameter is bound : $($PSCmdlet.MyInvocation.BoundParameters['Verbose'])"
            Write-Host "2: Module: verbose preference         : $VerbosePreference"

            # Write-Verbose will just work without any change
            Write-Verbose "Verbose"

            # Other commands need the $VerbosePreference passed in
            Set-Item -Path Env:\DEMONSTRATE_PASS_THRU `
                     -Value 'You can safely delete this variable' `
                     -Verbose:$VerbosePreference
        }

        function Test-ModulePreferencePassThru
        {
            [CmdletBinding(SupportsShouldProcess=$true)]
            param()

            Write-Debug   "DebugPreference: $DebugPreference"
            Write-Warning "WarningPreference: $WarningPreference"
            Write-Error   "ErrorActionPreference: $ErrorActionPreference"

            Set-Item -Path Env:\DEMONSTRATE_PASS_THRU `
                     -Value 'You can safely delete this variable' `
                     -Verbose:$VerbosePreference `
                     -WarningAction:$WarningPreference `
                     -ErrorAction:$ErrorActionPreference
        }
    } | Out-Null

function Test-Verbose
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param()

    Write-Host ("Verbose preference: $VerbosePreference")
    Test-ModuleVerbose -Verbose:$VerbosePreference
}

function Test-PreferencePassThru
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    param()

    Test-ModulePreferencePassThru -Verbose:$VerbosePreference
}

try
{
    if ($FullPassThru -eq $false)
    {
        # just demonstrate -verbose pass-through
        Test-Verbose
    }
    else
    {
        # most of the preferences can be explicitly passed-through, however:
        #
        #  -Debug  : $DebugPreference is automatically passed-through
        #            and -Debug forces $DebugPreference to 'Inquire'
        #  -WhatIf : automatically passed-through
        Test-ModulePreferencePassThru -Verbose:$VerbosePreference `
                                        -WarningAction:$WarningPreference `
                                        -ErrorAction:$ErrorActionPreference | Out-Null
    }
}
finally
{
    # cleanup
    Remove-Item -Path Env:\DEMONSTRATE_PASS_THRU -Force | Out-Null
}

Save the above as test.ps1. When invoked from the console:

PS C:\temp> .\test.ps1
Verbose preference: SilentlyContinue
1: Module: verbose parameter is bound : False
2: Module: verbose preference         : SilentlyContinue

PS C:\temp> .\test.ps1 -Verbose
VERBOSE: Exporting function 'Test-ModuleVerbose'.
VERBOSE: Exporting function 'Test-ModulePreferencePassThru'.
VERBOSE: Importing function 'Test-ModulePreferencePassThru'.
VERBOSE: Importing function 'Test-ModuleVerbose'.
Verbose preference: Continue
1: Module: verbose parameter is bound : True
2: Module: verbose preference         : Continue
VERBOSE: Verbose
VERBOSE: Performing the operation "Set Item" on target "Item: DEMONSTRATE_PASS_THRU Value: You can safely delete this variable".

Furthermore, pass-through for $DebugPreference, $WarningPreference and $ErrorActionPreference also works:

PS C:\temp> $VerbosePreference  = 'Continue'
PS C:\temp> $DebugPreference = 'Continue'
PS C:\temp> $WarningPreference = 'Continue'
PS C:\temp> $ErrorActionPreference = 'Continue'
PS C:\temp> .\test.ps1 -FullPassThru
VERBOSE: Exporting function 'Test-ModuleVerbose'.
VERBOSE: Exporting function 'Test-ModulePreferencePassThru'.
VERBOSE: Importing function 'Test-ModulePreferencePassThru'.
VERBOSE: Importing function 'Test-ModuleVerbose'.
DEBUG: DebugPreference: Continue
WARNING: WarningPreference: Continue
Test-ModulePreferencePassThru : ErrorActionPreference: Continue
At C:\OAASMain\Online\ContainerService\Tools\docker\test.ps1:72 char:9
+         Test-ModulePreferencePassThru -Verbose:$VerbosePreference `
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-ModulePreferencePassThru

VERBOSE: Performing the operation "Set Item" on target "Item: DEMONSTRATE_PASS_THRU Value: You can safely delete this variable".

-WhatIf is automatically passed-through:

PS C:\temp> .\test.ps1 -FullPassThru -WhatIf
What if: Performing the operation "Remove Item" on target "Item: DEMONSTRATE_PASS_THRU".

This also handles -WarningAction and -ErrorAction:

PS C:\temp> .\test.ps1 -FullPassThru -WarningAction Ignore -ErrorAction Stop
Test-ModulePreferencePassThru : ErrorActionPreference : Stop
At C:\OAASMain\Online\ContainerService\Tools\docker\test.ps1:72 char:9
+         Test-ModulePreferencePassThru -Verbose:$VerbosePreference `
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-ModulePreferencePassThru
Mike
  • 1,227
  • 10
  • 15
  • 5
    `-Verbose`is a switch, not a string/set parameter, so setting `-Verbose:$VerbosePreference` will result in an error `Cannot convert 'System.String' to the type 'System.Management.Automation.SwitchParameter' required by parameter 'Verbose'` (at least in PowerShell 7). The correct syntax to pass through Verbose is `-Verbose:($VerbosePreference -eq 'Continue')` – Björn Jarisch Feb 24 '22 at 16:40
11

There is a variable named $VerbosePreference you can check to see how Verbose output should be handled. However, scripts loaded into a separate scope is giving you the issues. If you read the Get-Help about_scopes, you'll see:

Script:
The scope that is created while a script file runs. Only the commands in the script run in the script scope. To the commands in a script, the script scope is the local scope.

You can add the script to the current scope instead using dot source notation. From the same help file, below the heading Using Dot Source Notation with Scope it is stated that:

Scripts and functions follow all the rules of scope. You create them in a particular scope, and they affect only that scope unless you use a cmdlet parameter or a scope modifier to change that scope.

But, you can add a script or function to the current scope by using dot source notation. Then, when a script runs in the current scope, any functions, aliases, and variables that the script creates are available in the current scope.

I suggest reading up more about scopes in the Get-Help about_scopes help chapter.

For a quick test of whether this works or not:

[CmdletBinding()]
PARAM()

New-Module -Name TempModule -ScriptBlock {
    function Show-ModuleVerbosePreference
    {
        [CmdletBinding()]
        PARAM()

        Write-Host "Verbose preference in module function: $VerbosePreference"
    }
} | Out-Null

function Show-ScriptVerbosePreference
{
    [CmdletBinding()]
    PARAM()

    Write-Host "Verbose preference in script function: $VerbosePreference"
}

Show-ScriptVerbosePreference
Show-ModuleVerbosePreference</pre>

And if we try to call this script file using different methods we get the following output:

PS C:\> .\verbosity.ps1
Verbose preference in script function: SilentlyContinue
Verbose preference in module function: SilentlyContinue

PS C:\> .\verbosity.ps1 -Verbose
VERBOSE: Exporting function 'Show-ModuleVerbosePreference'.
VERBOSE: Importing function 'Show-ModuleVerbosePreference'.
Verbose preference in script function: Continue
Verbose preference in module function: SilentlyContinue

PS C:\> . .\verbosity.ps1
Verbose preference in script function: SilentlyContinue
Verbose preference in module function: SilentlyContinue

PS C:\> . .\verbosity.ps1 -Verbose
VERBOSE: Exporting function 'Show-ModuleVerbosePreference'.
VERBOSE: Importing function 'Show-ModuleVerbosePreference'.
Verbose preference in script function: Continue
Verbose preference in module function: Continue

So by using dot source notation we have added the script scope into the current scope which seems to make the VerbosePreference setting visible in the module method as well.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Robert Westerlund
  • 4,750
  • 1
  • 20
  • 32
  • Thanks @robert.westerlund. I don't think I explained myself properly. I know how to check whether the Verbose flag is set within a script. What I need is to be able to check - from within a PowerShell module - to see whether a script was invoked with the Verbose flag. I've updated my question to explain it better. – Dan Jan 07 '14 at 01:02
  • Sorry I missunderstood you. What I think you'll need to look into is PowerShell scopes. I'll update my answer. – Robert Westerlund Jan 08 '14 at 01:18
  • 1
    Thanks @robert.westerlund. I think that dot-sourcing the scripts probably the closest I'm going to get. Does seem bizarre though - given that a module is designed to be re-usable bit of code called from numerous places, you'd have thought it would be possible to pick up script-scope variables. Thanks for your help. – Dan Jan 09 '14 at 01:42
  • I suggest reading up on scopes (Get-Help about_scopes) and you can why/how it works. :) I had to do the same, so perhaps you'll find a better solution than suggested above (and if so, please edit my answer). – Robert Westerlund Jan 09 '14 at 01:59
  • @Dan I believe my answer is addressing your question of how to pass the `-Verbose` and other common parameters all the way through. – Mike Apr 22 '17 at 09:53
  • Trying "`Get-Help about_scopes`", I got an error. But this was PowerShell's fault and running *[`Update-Help`](https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Core/Update-Help?view=powershell-5.0)* (as an administrator) fixed it. (Windows 10) – Peter Mortensen Dec 18 '18 at 07:17
2

In my .psm1, I place a command similar to this:

If ((Get-PSCallStack)[1].Arguments -like '\*Verbose=True\*') {
    Write-Host 'The .ps1 script importing this module is Verbose'
};

You may use the script block to set a variable, such as $VerbosePreference in your module scope, or your own unique variable for your own logic.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
-1

Try the ContainsKey method:

$PSCmdlet.MyInvocation.BoundParameters.ContainsKey('verbose')
Shay Levy
  • 121,444
  • 32
  • 184
  • 206
  • 1
    When executed from within the module, the BoundParameters collection always appears empty. – Dan Jan 09 '14 at 01:40