3

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"?

2 Answers 2

2

The root cause of your problem is a long-standing, known 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()
      }
      
Sign up to request clarification or add additional context in comments.

1 Comment

Maybe second workaround is not pretty, but it works. Thanks a lot!
1

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)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.