By: Tim Smith | Updated: 2018-12-18 | Comments | Related: > PowerShell
Problem
When we're debugging scripts in PowerShell, it can be useful to call information related to our script we're using. In this tip, we'll be looking at one object that PowerShell provides called MyInvocation which comes from the namespace of System.Management.Automation and we'll use it in multiple exercises where it may provide us details that assist us. Because this script can provide us with information about our script, we should consider when recording information about our script (along with other practices) may not be a best practice - such as strict security contexts.
Solution
For the images in this script, all come from a PowerShell ISE window. A PowerShell command prompt will also function (see the below image with the output), but since PowerShell ISE provides us with a script window and output window, we'll use it as we'll be writing some functions to test.
$MyInvocation
The PowerShell object $MyInvocation provides us with some properties that we can use in our functions to return information that may be helpful when we're trying to debug, or if we're using software tools that may wrap errors in statements which reduce our ability to debug quickly. Because some of these tools may be doing this for secure reasons, we should consider that this may be best practices for our environment. We'll assume in this tip that we're only debugging. As we see in the above image (from a PowerShell command line), when we call the object, most properties return with nothing. Part of this is because our script does nothing except call the $MyInvocation object.
The first property of $MyInvocation, MyCommand, returns a hash of information - one of which is the script itself. As we see in the above image from the PowerShell command prompt, when we only call the object with nothing else, the MyCommand property returns what appears to be the name of the object since nothing else existed in the script. In actuality, the script is returning the text of the script. This becomes clear when we call the MyCommand property of $MyInvocation - notice in the below image that it returns a hash of information, some of which we can dig into further, such as the Name and ScriptBlock. Since the Name is null, we get a blank, as we see the hash, a blank, then the ScriptBlock returned.
cls Write-Host "Nothing here" Write-Host "Nothing else here too" $MyInvocation.MyCommand $MyInvocation.MyCommand.Name $MyInvocation.MyCommand.ScriptBlock
Since we tend to use functions in PowerShell, we'll create two generic PowerShell functions that return a combination of string objects, such as a line, text and an input string variable. We'll lead these functions by returning the Name details of MyCommand. As we see in the below script, in addition to seeing the expected output, we see the names of our generic functions.
Function Return-GenericOne { [CmdletBinding()] Param( [string]$generic ) Process { $MyInvocation.MyCommand.Name Write-Host ([Environment]::NewLine + "Returning: $generic") } } Function Return-GenericTwo { [CmdletBinding()] Param( [string]$generic ) Process { $MyInvocation.MyCommand.Name Write-Host ([Environment]::NewLine + "Returning: $generic") } } Return-GenericOne -generic "1" Return-GenericTwo -generic "2"
We'll now take these same two functions in a new PowerShell ISE script window, except this time make a change by removing the [CmdletBinding()] on the second function. Notice what properties return on further analysis of the MyCommand details and we'll see in the below images not only the output, but how the first script will have extra parameters allowed in it (second image below), while the second script does not (third image below this). These additional parameters may be useful in debugging, so it's worth noting this behavior if we want to use these parameters. We also see the output of various properties differing - the CmdletBinding is true for the first script, but not the second, both remote's capability is PowerShell, and the Key-Value pairs for the parameters in the first script are more than what we specific because the CmdletBinding is true.
Function Return-GenericOne { [CmdletBinding()] Param( [string]$generic ) Process { $MyInvocation.MyCommand.CmdletBinding $MyInvocation.MyCommand.RemotingCapability $MyInvocation.MyCommand.Parameters Write-Host ([Environment]::NewLine + "Returning: $generic") } } Function Return-GenericTwo { Param( [string]$generic ) Process { $MyInvocation.MyCommand.CmdletBinding $MyInvocation.MyCommand.RemotingCapability $MyInvocation.MyCommand.Parameters Write-Host ([Environment]::NewLine + "Returning: $generic") } } Return-GenericOne -generic "1" Return-GenericTwo -generic "2"
We can add further options to the [CmdletBinding()] addition, but for the sake of this tip we'll observe the behavior of what this allows with the first script compared to the second script. According to Microsoft, this feature makes the script function like compiled C# code. With the additional parameters, we can specify additional options, such as specifying Inquire on InformationAction. This is something to consider adding to scripts if we want these additional options along with restrictions provided by [CmdletBinding()].
While we may want to return a full script block, as we see above this, we may not want that information in debugging. We may only want the script name, so knowing which details to choose in MyCommand can help us return the information we want. Along with the name, it may be helpful to get the parameters that our function requires. $MyInvocation provides us with a dictionary of bound parameters. In the below script, we create a third generic function with three required parameters and call it outputting the keys and values of the parameters, along with their positional binding. Notice the effects of running this script in PowerShell ISE when we specify the parameters with a specific order by using their names (Return-GenericThree -genericthree "3" -genericone "1" -generictwo "2") and when we simply pass in parameters (Return-GenericThree "3" "1" "2").
Function Return-GenericThree { [CmdletBinding()] Param( [Parameter(Mandatory=$true,Position=1)][string]$genericone , [Parameter(Mandatory=$true,Position=2)][string]$generictwo , [Parameter(Mandatory=$true,Position=3)][string]$genericthree ) Process { Write-Host ("Count: " + $MyInvocation.BoundParameters.Count) $MyInvocation.BoundParameters.Keys $MyInvocation.BoundParameters.Values $MyInvocation.BoundParameters.BoundPositionally Write-Host ("$genericone, $generictwo, $genericthree") } } Return-GenericThree "3" "1" "2" Write-Host ([Environment]::NewLine + "Demarcated:" + [Environment]::NewLine) Return-GenericThree -genericthree "3" -genericone "1" -generictwo "2"
We'll note that we don't have to specify the parameter names (the first function's attempt), though we'll notice that the first function which calls Return-GenericThree "3" "1" "2" differs in output from the second function which calls Return-GenericThree -genericthree "3" -genericone "1" -generictwo "2" because we have created an order to our parameters, so without specification, the order determines the binding of which passed in value aligns with which parameter. In the second script, we specify the parameters (order doesn't matter since these are specified as long as all mandatory parameters are provided). We also see that the $MyInvocation.BoundParameters.BoundPositionally returns nothing in the second script because we specified the binding by specifying the parameter order (-genericthree "3" -genericone "1" -generictwo "2"), while it returns genericone generictwo genericthree in the first function as without the binding, this is the order of the parameters. We can execute PowerShell functions without specifying, but we have to then follow the order and syntax exactly. If we work in an environment where it's common practice to not specify parameters, this may be a useful output for debugging. While I tend to avoid specifying parameters in my functions to reduce confusion, this is not always common practice.
As we see with some of the operations in the $MyInvocation object, we can use some of its properties to assist us as we're debugging scripts, or for returning relevant information in some development contexts. Since this object provides us with meta-information about our script, we can re-use our catch structure in many scripts during our debugging or testing script phrase.
Next Steps
- We can use $MyInvocation to help us quickly debug our scripts before releasing them by getting relevant information for our scripts.
- As we see in the above tip, one practice with $MyInvocation is to call it within the catch block. For debugging, we could call this information at the beginning or end of a script as an alternative.
- In general, logging information can provide helpful details to solve development problems. However, logging information can provide useful information to infiltrators which helps them identify the structure or design of what they're attacking or breaching. Consider when it's appropriate to log information along with how you log information.
About the author
This author pledges the content of this article is based on professional experience and not AI generated.
View all my tips
Article Last Updated: 2018-12-18