Question:
Is there a way that I can re-define a function that was exported by a module and is used by another?
Given the following folder structure:
1 2 3 4 5 6 7 8 9 10 |
C:. │ MyScript.ps1 │ └───MyModules ├───Deployment │ Deployment.psm1 │ └───Logging Logging.psm1 |
And the following content for each file:
MyScript.ps1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#Add MyModules to the module path $cleanedModulePath = @() $env:PSModulePath -split ";" | %{ if( !( $_ -Match "MyModules") ){ $cleanedModulePath += $_ } } $thisDir = (Split-Path -parent $script:MyInvocation.MyCommand.Definition) $moduleDir = Join-Path $thisDir "MyModules" $cleanedModulePath += $moduleDir $env:PSModulePath = ($cleanedModulePath -Join ";") Import-Module Deployment -DisableNameChecking #Neither of these manage to redefine Log-Debug - even if put before the Import function Log-Debug{} Set-Item -Path function:Log-Debug -Value {} -Option "AllScope,ReadOnly" -Force Do-Something |
Deployment.psm1:
1 2 3 4 5 6 7 8 9 10 |
Import-Module Logging -DisableNameChecking function Do-Something { Log-Debug "Do-Something outputting a message via Log-Debug" Write-Host "Do-Something outputting a message directly" } Export-ModuleMember -Function Do-Something |
and Logging.psm1:
1 2 3 4 5 6 7 |
function Log-Debug { param([string] $message) Write-Host $message } Export-ModuleMember -Function Log-Debug |
The output is:
1 2 3 4 |
C:\Temp\Deploy2>powershell .\MyScript.ps1 Do-Something outputting a message via Log-Debug Do-Something outputting a message directly |
but I would like it to be:
1 2 3 |
C:\Temp\Deploy2>powershell .\MyScript.ps1 Do-Something outputting a message directly |
I realize that a better implementation of the Logging module would allow me to control the log level, but that’s not the point of this question: is it possible to redefine/override Log-Debug so that it is a no-op?
As noted in the source of MyScript.ps1, I have tried to redefine the function using Set-Item and just redeclaring the function to no avail….
Answer:
I would’ve guessed that simply defining an empty function with the same name should suffice. And it actually does, when both modules are imported directly in PowerShell:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
PS C:\> $modules = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules" PS C:\> Get-Content "$modules\Logging\Logging.psm1" function Log-Debug($msg) { Write-Host "DEBUG: $msg" } Export-ModuleMember -Function Log-Debug PS C:\> Get-Content "$modules\Deployment\Deployment.psm1" function Foo($msg) { Write-Output 'FOO: something' Log-Debug $msg } Export-ModuleMember Foo PS C:\> Import-Module Logging -DisableNameChecking PS C:\> Import-Module Deployment PS C:\> Foo 'bar' FOO: something DEBUG: bar PS C:\> function Log-Debug {} PS C:\> Foo 'bar' FOO: something PS C:\> _ |
But for some reason you cannot re-define or remove a function that was imported from a module by another module (or at least I didn’t find a way to do so).
However, you should be able to override the function by making use of command precedence. Since aliases are evaluated first, you can define an alias Log-Debug
to a custom logging function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
PS C:\> $modules = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules" PS C:\> Get-Content "$modules\Logging\Logging.psm1" function Log-Debug($msg) { Write-Host "DEBUG: $msg" } Export-ModuleMember -Function Log-Debug PS C:\> Get-Content "$modules\Deployment\Deployment.psm1" Import-Module Logging -DisableNameChecking function Foo($msg) { Write-Output 'FOO: something' Log-Debug $msg } Export-ModuleMember Foo PS C:\> Import-Module Deployment PS C:\> Foo 'bar' FOO: something DEBUG: bar PS C:\> function Log-DebugSuperseded {} PS C:\> Set-Alias -Name Log-Debug -Value Log-DebugSuperseded PS C:\> Foo 'bar' FOO: something PS C:\> _ |
That way you can even change the logging implementation on the fly, or switch back and forth between original and custom logging function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
PS C:\> Foo 'bar' FOO: something DEBUG: bar PS C:\> function Log-DebugSuperseded {} PS C:\> Set-Alias -Name Log-Debug -Value Log-DebugSuperseded PS C:\> Foo 'bar' FOO: something PS C:\> function Log-DebugSuperseded {'baz'} PS C:\> Foo 'bar' FOO: something baz PS C:\> Remove-Item Alias:\Log-Debug PS C:\> Foo 'bar' FOO: something DEBUG: bar PS C:\> _ |
Edit:
To make this work in a script you need to define both function and alias as global objects:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
PS C:\> Get-Content .\test.ps1 Import-Module Deployment Write-Host '--- without alias ---' Foo 'bar' function global:Log-DebugSuperseded {} Set-Alias -Name Log-Debug -Value Log-DebugSuperseded -Scope global Write-Host '--- with alias ---' Foo 'bar' PS C:\> ./test.ps1 --- without alias --- FOO: something DEBUG: bar --- with alias --- FOO: something PS C:\> _ |