In-Depth

How To Build a Better PowerShell Test-Connection Function with Proxy Commands

It's easier to modify an existing cmdlet to your needs than writing one from scratch.

Are all of the default PowerShell cmdlets exactly the way you like them to be? Do they have all of the parameters you think should be standard and have no extras? Probably not. We all have our quirks and the way we like things and fortunately for us, we don't have to just get used to the default PowerShell cmdlets. We can bend them to our will!

Whenever you have a PowerShell cmdlet that you feel is almost perfect but requires a little tweaking you can easily make a copy and change it to your heart's desire. I'm not talking about just calling the cmdlet from within your wrapper function. I'm talking about making an exact clone of the compiled binary cmdlet and inserting any changes into it. We can accomplish this through proxy commands.

To explain the concept of proxy commands, let's use an example. I'm going to use the default cmdlet Test-Connection. I'd like a better error message displayed when Test-Connection cannot resolve a name passed to it. I want to run Resolve-DnsName before Test-Connection is truly ran and output a better error message. To do this, I need a way to run Resolve-DnsName on the parameter value that always passed to Test-Connection's ComputerName parameter. I can make this happen by creating a proxy Test-Connection function.

To create a proxy command, I'll first need to capture the command metadata present in the Test-Connection cmdlet. I can do this by setting up a CommandMetaData object.

$MetaData = New-Object System.Management.Automation.CommandMetaData (Get-Command -Name Test-Connection)

Once I have this, I can create my proxy command.

[System.Management.Automation.ProxyCommand]::Create($MetaData)

This will send the entire function to the console as output. We need to capture this function, so I'll send it to Out-File to capture it into its own script.

[System.Management.Automation.ProxyCommand]::Create($MetaData) | Out-File 'Test-ConnectionProxy.ps1'

This will create a script with all of the guts necessary to reproduce the Test-Connection cmdlet.

When you open this up, you will see everything that makes up that cmdlet. Here you can add or remove parameters or inject code to run before or after the "real" Test-Connection runs. In our example, I'd like to execute some code before the Test-Connection cmdlet runs.

To do this, I'll scroll down until I see the begin block as you see below:

[Click on image for larger view.] Figure 1.

The begin block is where the actual Test-Connection is executed as you can see there by lines 62-65. I'm going to insert some code before that to attempt to resolve the value of ComputerName passed to Test-Connection. The resulting begin block is below:

[Click on image for larger view.] Figure 2.

Notice I'm capturing what was passed to the $ComputerName parameter and passing it to Resolve-DnsName. If Resolve-DnsName can't resolve it, it will immediately throw an exception. If not, it will continue on and run Test-Connection as you'd expect.

The code changes are complete, but our proxy command is not finished yet. We must now wrap the code in this script to create our own Test-Connection function. To do so, simply add the typical "function Test-Connection {" at the top of the script and the closing brace at the bottom.

Function Test-Connection {

## Script contents here

}

Be sure to name it Test-Connection so that it will override the default Test-Connection cmdlet. Now, either copy and paste this function into your console, place it in your profile or dot source the script into your session. It doesn't matter. As long as the function is available in your session, it will work.

Now whenever you run Test-Connection, your proxy command will be the first to run instead of the built in Test-Connection cmdlet. By doing so, you will ensure that every time Test-Connection is executed, it will always run Resolve-DnsName before running the actual Test-Connection cmdlet and will give you that error message you were looking for.

A proxy command can be built from any built-in cmdlet. They are an excellent way to modify the behavior of any cmdlet without having to write your own or figure out how to decompile the code and write your own compiled cmdlet in C#. The next time you need to add some functionality to a cmdlet, use a proxy command. Don't just create your own function and call the original. This is a much cleaner and reliable method of "wrapping" functions around compiled cmdlets.