Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verbose in class with "using module" directive

I faced a specific behavior while using verbose output in class.

This works like a charm:

# SomeModule.psm1
class SomeClass {
    SomeClass() {
        Write-Verbose "It works!"
    }
}

function Test-SomeModule {
    [CmdletBinding()]
    param()
    [SomeClass]::new()
}

And console output looks as expected:

PS C:\> Test-SomeModule -Verbose
VERBOSE: It works!
PS C:\> 

But! if we move the class to a separate file and import it to root module:

# SomeClass.psm1
class SomeClass {
    SomeClass() {
        Write-Verbose "It works!"
    }
}
# SomeModule.psm1
using module .\SomeClass.psm1

function Test-SomeModule {
    [CmdletBinding()]
    param()
    [SomeClass]::new()
}

There will be no verbose out, even -Verbose parameter is specified:

PS C:\> Test-SomeModule -Verbose
PS C:\> 

Can anybody explain me this? Is there any way to passthrough verbose preference to the class, imported with "using module"?

like image 692
Aleksej Egorov Avatar asked Aug 14 '26 20:08

Aleksej Egorov


2 Answers

The root cause of your problem is a long-standing, known problem:

  • In advanced functions, common parameters such as -Verbose are translated to function-local instances of preference variables with generally equal effect, such as $VerbosePreference = 'Continue'

  • However, preference variables are not seen across scope-domain ("session state") boundaries, so that a class definition from a (different) module does not respect them - which explains your symptom.

    • See GitHub issue #4568 for a discussion of this long-standing problem.

Workarounds:

  • PowerShell (Core) 7 (v7.4+) only:

    • Decorate your class definition with the [NoRunspaceAffinity()] attribute, which prevents it from binding to the scope domain of the module of origin and instead binds it to the caller's scope domain on instantiation of the class:

      # SomeClass.psm1 - PowerShell (Core) 7.4+ only
      [NoRunspaceAffinity()]
      class SomeClass {
          SomeClass() {
              Write-Verbose "It works!"
          }
      }        
      
    • The caller's code can remain the same as in your question.

  • Alternative that also works in Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and final version is 5.1):

    • Define your class in a *.ps1 (regular script file) rather than a *.psm1 file (script-module file).

      # SomeClass.ps1 - NOTE: .ps1 rather than .psm1
      class SomeClass {
          SomeClass() {
              Write-Verbose "It works!"
          }
      }        
      
    • Then, in the calling code, use dot-sourcing rather than using module in order to import your class definition from the .ps1 file (dot-sourced *.ps1 scripts implicitly run in the same scope domain as the caller):

      # Caller code
      
      # Dot-source the *.ps1 file with the class definition(s)
      . .\SomeClass.ps1
      
      function Test-SomeModule {
          [CmdletBinding()]
          param()
          [SomeClass]::new()
      }
      
like image 57
mklement0 Avatar answered Aug 16 '26 18:08

mklement0


Another workaround could be to pass the cmdlet as parameter of your constructor and use it instead of Write-Verbose to stream verbose output, this wouldn't require any change to the file structure (both can be .psm1) but would require adding the parameter in your class .ctor and passing $PSCmdlet when creating the instance of your class.

Additionally, this approach will respond correctly to -Verbose and $VerbosePreference.

# SomeClass.psm1
class SomeClass {
    SomeClass([System.Management.Automation.PSCmdlet] $cmdlet) {
        # $cmdlet could be stored in a field to use in other methods
        $cmdlet.WriteVerbose('It works!')
    }
}

# SomeModule.psm1
using module .\SomeClass.psm1

function Test-SomeModule {
    [CmdletBinding()]
    param()
    [SomeClass]::new($PSCmdlet)
}
like image 21
Santiago Squarzon Avatar answered Aug 16 '26 19:08

Santiago Squarzon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!