Tag Archives: Get-PSCallStack

Get-PSCallStack Demonstration Using Multiple Functions

I wrote a couple recent posts that used the Get-PSCallStack cmdlet. In my case, Get-PSCallStack helped one function determine what function invoked it, and used the information to further determine if that function was approved to invoke it, or not. In doing this, I wrote a series of small functions I’d like to share to help demonstrate this process.

In the below code example, I’ve defined a series of functions. The first function defined is called Get-Main, and it does a few things. It sets a variable $Index to zero, dumps the Get-PSCallStack commands into a global variable (that will exist after the function completes), and then loops through each of the command properties in the second variable, displaying its correctly assumed index, and the command name.

The following functions are in place to progressively call one another from the bottom up. Working downward, however, Get-OneUpOne invokes Get-Main (discussed above), Get-OneUpTwo invokes Get-OneUpOne, Get-OneUpThree invokes Get-OneUpTwo, etc. These multiple functions end up being listed in Get-PSCallStack, as we’ll soon see.

Function Get-Main {
    $Index = 0
    $Global:Var = (Get-PSCallStack).Command
    $Var | ForEach-Object {
        "Index $Index : $_"
        $Index ++
    }
}

Function Get-OneUpOne {
    Get-Main
}

Function Get-OneUpTwo {
    Get-OneUpOne
}

Function Get-OneUpThree {
    Get-OneUpTwo
}

Function Get-OneUpFour {
    Get-OneUpThree
}

Get-OneUpFour

The last line in the above example, starts the magic by invoking Get-OneUpFour, that invokes Get-OneUpThree, etc., moving upward. Here’s a gif that shows how these are invoked, working up from the bottom.

get-pscallstack-demonstration-using-multiple-functions01

When this chunk of code is dropped into the ISE and run, we get the results listed below. They indicate the index and value of Get-PSCallStack’s recorded commands, or steps, on the way to invoking the Get-Main function. Notice, that it’s backwards, and what I mean is this: The first index 0, is the last command recorded. The last index, 5, is the first command recorded.

Index 0 : Get-Main
Index 1 : Get-OneUpOne
Index 2 : Get-OneUpTwo
Index 3 : Get-OneUpThree
Index 4 : Get-OneUpFour
Index 5 : <ScriptBlock>

Since we created the global variable $Var, we can verify if the above results are correct — if our assumed index is accurate. We’ll check with a single index first, and then run $Var though a For loop to see each index.

$Var[2]

Get-OneUpTwo
For ($i = 0; $i -lt $Var.Length; $i++) {
    "Index $i -- $($Var[$i])"
}

Index 0 -- Get-Main
Index 1 -- Get-OneUpOne
Index 2 -- Get-OneUpTwo
Index 3 -- Get-OneUpThree
Index 4 -- Get-OneUpFour
Index 5 -- Get-PSCallStack_Example.ps1

Take a look at index 5 in the directly above example, and then the further above example. One says, “<ScriptBlock>,” and one says, “Get-PSCallStack_Example.ps1.” Any idea what changed between these two examples? If you guessed that I saved my code example, while writing this post, then you’d be correct.

The highest numbered index* — 5 in this example — is always going to be the starting point — whether it’s called from the prompt, or a saved file, and the lowest index — index 0 — will always be the currently invoked function. This means that index 1 will always be the calling function, of the last function that was invoked.

* The highest numbered index will also always be index -1, such as $Var[-1] would equal “<ScriptBlock>,” or “Get-PSCallStack_Example.ps1.” Knowing this, it’s possible to work backwards through these results, or any other array.

So it’s been said, the other time we see an array in reverse, is the $Error variable. $Error[0] is always the most recent error, not the first error. Thanks for reading, and we’ll do it again sometime soon.

Specify Which Functions Can Invoke Another Function Part II

It wasn’t long after I wrote and posted Part I of this topic — which I didn’t know was a Part I at the time — that I had another idea.

For those that didn’t read the post linked above, I have a function called Send-ErrorMessage. Its purpose is to send an email if the catch block of a try-catch, that doesn’t include an error exception, executes. It’s a safety net, just in case I haven’t properly coded for an unknown error condition. Because I had to export the function, so that it’s available to other functions in other modules, I needed a way to ensure that only specific functions were able to invoke Send-ErrorMessage. I didn’t want a user using the function; I only wanted the functions I wrote, that were approved, to be able to use the Send-ErrorMessage function.

Well today, I took the conditional logic that was taking up space in my Begin block and moved it to the [ValidateScript()] attribute. This also helped clean up some of the Process block, too. The only thing that I’m giving up, is that now the message to someone trying to invoke this function with an unapproved function, or at the prompt, is that it’ll show an error using red text, instead of a warning using orange text (in the ISE).

I may have mentioned this before, but I believe it’s okay to display standard, red text error messages in your tools, depending on the audience. If I’m writing tools for my team, they should be comfortable enough to decipher PowerShell errors. If the tool is being written for end-users, or lower tier employees, then it may be wise to better control the errors and warnings.

The complete, modified code is below. It begins by defining the Send-ErrorMessage function. Then it creates two additional functions — one that is approved to invoke Send-ErrorMessage, and one that’s not. Then, it invokes those two functions — Test-Allowed and Test-NotAllowed, respectively — and displays the results one right after the other. The first function will be permitted and will write the verbose statements in the Process block, but the second function will fail inside the [ValidateScript()] attribute, as it’s not approved to invoke Send-ErrorMessage.

Copy, paste, and take it for a run. After you’ve run the code, enter Send-ErrorMessage manually. It still won’t let you run it from the command line, even if you provide an approved function name, as the value of the -FunctionName parameter. That’s right, you can’t fake it out, as it’s not taking your word, it’s using the value returned by Get-PSCallStack. Thanks for reading!

Function Send-ErrorMessage {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({
            $AcceptableFunctions = 'Get-AJob','Test-Allowed','Get-Real'
            $CallingFunction = (Get-PSCallStack)[1].Command
            If (-Not ($CallingFunction -in $AcceptableFunctions)) {
                Throw "The `"$_`" function or command, does not have approval to invoke this function. Send-ErrorMessage will not work if you use the function manually and enter an approved function name."
            } Else {
                $true
            }
        })]
        [string]$FunctionName
    )

    Begin {
    } # End Begin.

    Process {
        Write-Verbose -Message "The calling function ($($PSBoundParameters.FunctionName)) is approved." -Verbose
        Write-Verbose -Message 'Beginning to send error message ...' -Verbose
    } # End Process.
    
    End {
    } # End End.
} # End Function: Send-ErrorMessage.


Function Test-Allowed {
    # Divide by 0 to force error/catch block.
    try {
        10 / 0
    } catch {
        Send-ErrorMessage -FunctionName $MyInvocation.MyCommand.Name
    }
} # End Function: Test-Allowed.


Function Test-NotAllowed {
    # Divide by 0 to force error/catch block.
    try {
        10 / 0
    } catch {
        Send-ErrorMessage -FunctionName $MyInvocation.MyCommand.Name
    }
} # End Function: Test-NotAllowed.


# Invoke Function.
Test-Allowed

# Invoke Function.
Test-NotAllowed

Specify Which Functions Can Invoke Another Function

I have a function called Send-ErrorMessage. Its sole purpose is to send me an email if the catch block of a try-catch statement executes. Now, not just any catch block, as there can be more than one, but one where a specific error exception is not included.

The below example obtains a random Exchange server name as part of its try block. If that command produces an error, one of two catch blocks will execute. The first catch block will execute if the error produces a Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException exception. The exception is listed in square brackets after the catch keyword. The second catch block will execute if it’s any other exception. This, is the error I want to know about. When the code in the second catch block executes, it first creates a parameter hash table and it then sends that to the Send-ErrorMessage function, where the error is presented to the user, and an email is sent.

try {
    # Randomly select Exchange Server.
    $ExchServ = Get-Random (Get-ADGroupMember $ADGroup |
       Where-Object -FilterScript {$_.objectClass -eq 'computer'}).Name

} catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
    Write-Warning -Message "Cannot find object with the identity $ADGroup."

} catch {
    # Unknown error exception.
    $ErrorParams = @{
        FunctionName = $MyInvocation.MyCommand.Name
        ErrorMessage = $Error[0].ToString()
        ErrorType = $Error[0].Exception.GetType().FullName
    }
    Send-ErrorMessage @ErrorParams
}

At first, I decided that the Send-ErrorMessage function wasn’t to be exported from its module. This meant that only the other functions in that module, would be able to use Send-ErrorMessage. This was done to prevent a human from directly interacting with the Send-ErrorMessage function. In time, I had a decision to make, as I decided I wanted to invoke Send-ErrorMessage from functions that weren’t in the same module, where Send-ErrorMessage was declared.

To do this, I had to export Send-ErrorMessage, thus making it available to all my functions regardless of module, and anyone that invoked the function manually. It was this second part I wanted to prevent; I didn’t want a user to be able to use this function. I only wanted specific functions, in varying modules, to be allowed to use the Send-ErrorMessage function.

What I needed was a way to prevent the function from doing much, if it was invoked by something other than an approved function. To do this required me to know what function invoked the Send-ErrorMessage function while the Send-ErrorMessage function was currently being invoked. I hadn’t considered this before, and so it took some thinking and testing. In the end, I have a solution.

To help demonstrate, let’s start with two functions: The first one is allowed to invoke Send-ErrorMessage, and the second one is not allowed to invoke Send-ErrorMessage. You’ll see this in the upcoming Send-ErrorMessage function. Before we get to that function, though, get comfortable with the two below functions.

Function Test-Allowed {
    # Divide by 0 to force error/catch block.
    try {
        10 / 0
    } catch {
        Send-ErrorMessage
    }
} # End Function: Test-Allowed.


Function Test-NotAllowed {
    # Divide by 0 to force error/catch block.
    try {
        10 / 0
    } catch {
        Send-ErrorMessage
    }
} # End Function: Test-NotAllowed.

They work this way: try to divide by 0 and intentionally error. The catch block will then invoke Send-ErrorMessage. Inside Send-ErrorMessage is where the determination is made as to whether or not the function that invoked it, is allowed to continue to use it. If the function that invoked it, is in the $AcceptableFunctions variable, then the code to send an email with the error information, which isn’t actually included in the below function, can be sent. If the function isn’t in the $AcceptableFunctions variable, then the user will get a warning and nothing will be sent.

Function Send-ErrorMessage {
    [CmdletBinding()]
    Param (
    )

    Begin {
        # This variable can be set a number of ways to include Get-Command.
        $AcceptableFunctions = 'Get-AJob','Test-Allowed','Get-Real'
        $CallingFunction = (Get-PSCallStack)[1].Command
        
        $Proceed = $false
        If ($CallingFunction -in $AcceptableFunctions) {
            $Proceed = $true
        }
    } # End Begin.

    Process {
        If ($Proceed) {
            Write-Verbose -Message "The calling function ($CallingFunction) is approved." -Verbose
            Write-Verbose -Message 'Beginning to send error message ...' -Verbose
            # DO STUFF!!!
        } Else {
            Write-Warning -Message "The calling function ($CallingFunction) is NOT approved."
            Write-Warning -Message "This function ($($MyInvocation.MyCommand.Name)) can only be invoked by approved functions."
        }
    } # End Process.
    
    End {
    } # End End.
} # End Function: Send-ErrorMessage.

Let’s invoke Send-ErrorMessage now, by invoking both the Test-Allowed and Test-NotAllowed functions. In the below example, the first two lines, that start with VERBOSE, are returned from invoking the Test-Allowed function, and the last two lines, that start with WARNING, are returned from invoking the Test-NotAllowed function. It works just like it should, allowing me to do something (send that error email), when I trust the calling function.

# Invoke Function.
Test-Allowed

# Invoke Function.
Test-NotAllowed

VERBOSE: The calling function (Test-Allowed) is approved.
VERBOSE: Beginning to send error message ...
WARNING: The calling function (Test-NotAllowed) is NOT approved.
WARNING: This function (Send-ErrorMessage) can only be invoked by approved functions.

Here’s the entire code, so you can copy it to the ISE, etc. and test it for yourself.

Function Send-ErrorMessage {
    [CmdletBinding()]
    Param (
    )

    Begin {
        # This variable can be set a number of ways to include Get-Command.
        $AcceptableFunctions = 'Get-AJob','Test-Allowed','Get-Real'
        $CallingFunction = (Get-PSCallStack)[1].Command
        
        $Proceed = $false
        If ($CallingFunction -in $AcceptableFunctions) {
            $Proceed = $true
        }
    } # End Begin.

    Process {
        If ($Proceed) {
            Write-Verbose -Message "The calling function ($CallingFunction) is approved." -Verbose
            Write-Verbose -Message 'Beginning to send error message ...' -Verbose
            # DO STUFF!!!
        } Else {
            Write-Warning -Message "The calling function ($CallingFunction) is NOT approved."
            Write-Warning -Message "This function ($($MyInvocation.MyCommand.Name)) can only be invoked by approved functions."
        }
    } # End Process.
    
    End {
    } # End End.
} # End Function: Send-ErrorMessage.


Function Test-Allowed {
    # Divide by 0 to force error/catch block.
    try {
        10 / 0
    } catch {
        Send-ErrorMessage
    }
} # End Function: Test-Allowed.


Function Test-NotAllowed {
    # Divide by 0 to force error/catch block.
    try {
        10 / 0
    } catch {
        Send-ErrorMessage
    }
} # End Function: Test-NotAllowed.


# Invoke Function.
Test-Allowed

# Invoke Function.
Test-NotAllowed

My first thought after these functions were written and tested, was what happens now that Send-ErrorMessage is in memory. Can I, as a user, just invoke Send-ErrorMessage myself? I can try, but I won’t get anywhere, as is demonstrated below.

Send-ErrorMessage
WARNING: The calling function (<ScriptBlock>) is NOT approved.
WARNING: This function (Send-ErrorMessage) can only be invoked by approved functions.

Keep in mind other ways to assign the $AcceptedFunctions variable. This doesn’t, and realistically, shouldn’t need to be a static. Instead, use the Get-Command cmdlet to collect the functions that will be allowed to invoke Send-ErrorMessage. If you use a prefix in your function names, it’s easy to isolate which functions can invoke Send-ErrorMessage. The below example assumes you prefix the nouns in your functions with Work, such as Get-WorkUser, and Get-WorkJob.

$AcceptableFunctions = (Get-Command -Noun Work*).Name

In closing, if you didn’t pick it up yourself, this works in thanks to the Get-PSCallStack function. It stores the called commands in a reverse array. That means, that the final function, Send-ErrorMessage, in this example, is index 0: (Get-PSCallStack)[0]. Therefore, the calling function, or command run in the console, is going to be (Get-PSCallStack)[1]. Tada! And to think, just a day or two ago, and I don’t think I had ever used the Get-PSCallStack cmdlet.

Update: There’s a Part II: Read it here.