Override functions multiple times for inheritant call stack logging

106 Views Asked by At

Let's assume the following CmdLets:

Write-MyColl

function Write-MyColl {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [psobject] $InputObject
    )

    $verb = Get-Command Write-Verbose
    $fixedName = $PSCmdlet.MyInvocation.InvocationName
    function Write-Verbose ($Object) {
        & $verb "[$fixedName] $Object"
    }

    if ($InputObject.name) {
        Write-Verbose "Writing `$InputObject's Name: `"$($InputObject.name)`""
    }
}

New-MyColl

function New-MyColl {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]] $Names
    )

    $verb = Get-Command Write-Verbose
    $fixedName = $PSCmdlet.MyInvocation.InvocationName
    function Write-Verbose ($Object) {
        & $verb "[$fixedName] $Object"
    }

    Write-Verbose "Names: $Names"
    foreach ($name in $Names) {
        @{
            id = 123;
            name = $name
        } | Write-MyColl
    }
}

As you can see New-MyColl overrides Microsoft.PowerShell.Utility\Write-Verbose with it's own Write-Verbose. This works perfectly and outputs:

VERBOSE: [New-MyColl] Names: [...]

Now, when Write-MyColl is invoked it should override Function:\Write-Verbose from New-MyColl and I want it to output something like:

VERBOSE: [New-MyColl] [Write-MyColl] Writing `$InputObject's Name: "[...]"

As you might already think, it doesn't work. Write-Verbose recursively calls itself until the end of days. I already tried local function definitions and local variables, but they won't be visible to the invoked CmdLet.

I think for my specific use-case, I will use Get-PsCallStack, however I'd love to see a solution to override overriden functions like described above.

What's hard for me to understand is, why the 2nd Write-Verbose calls itself, so $verb seemingly points to a function, which is not yet defined. Shouldn't it point to the prior defined function? I already tried to Remove-Item the funtion at the end of the script, but it didn't change anything.

Do you have any idea how to achieve this call-stack-behavior?

1

There are 1 best solutions below

0
On BEST ANSWER

Thanks to PetSerAI's comment, I came to the following solution:

New-Item function::local:Write-Verbose -Value (
    New-Module -ScriptBlock { param($verb, $fixedName, $verbose) } -ArgumentList @(
        (Get-Command Write-Verbose), 
        $PSCmdlet.MyInvocation.InvocationName, 
        $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent
    )
).NewBoundScriptBlock{
    param($Message)
    if ($verbose) {
        & $verb -Message "=>$fixedName $Message" -Verbose
    } else {
        & $verb -Message "=>$fixedName $Message"
    }
} | Write-Verbose

The -Verbose Parameter isn't passed to the newly created function; that's why I added it to the -ArgumentList. I'm sure, it's not very pretty, but it does exactly what it should. Overwrite Write-Verbose for each call.

Now the output looks like:

VERBOSE: =>[New-MyColl] Names: [...]
VERBOSE: =>[New-MyColl]=>[Write-MyColl] Writing `$InputObject's Name: "[...]"