Prof. Powershell
The PowerShell Blacksmith Part 7: Proxy Functions
The final entry in this column series will show you how to fully customize your PowerShell tool.
- By Jeffery Hicks
- 11/05/2013
In the last part of this series, we looked at defining a type for the output of our PowerShell tool. If you skipped that lesson, you should go back and read it first. I'll wait... Ok. Today we'll look at another toolmaking technique, the proxy function. The purpose of a proxy function is to take an existing command and customize to better suit your needs. Perhaps you need to remove a parameter or add your own. In my case, I want to create a tool based on Measure-Object where I can specify that the values be formatted as KB, MB, GB or TB. Here's how.
First, I need to use a little .NET magic to get information about Measure-Object.
$metadata = New-Object System.Management.Automation.CommandMetaData (Get-Command Measure-Object)
Next, using a bit more .NET magic I'm going to create a proxy command based on the metadata. The command will write to the pipeline so I'm simply redirecting to a script file which I can open in the PowerShell ISE.
[System.Management.Automation.ProxyCommand]::Create($metadata) | Out-File C:\Scripts\MyMeasure-Object.ps1
Some IT Pros will create a proxy function that will replace the original function. Instead, I'm going to create my own tool based on Measure-Object. I'll wrap the output in a Function declaration. I'll call my version Measure-MyObject. Because I am making my own command, in cmdlet binding I'll delete references to the Help URI. I also don't need the RemotingCapability.
[CmdletBinding(DefaultParameterSetName='GenericMeasure', HelpUri='http://go.microsoft.com/fwlink/?LinkID=113349', RemotingCapability='None')]
The same is true of the forward help lines at the end.
<#
.ForwardHelpTargetName Measure-Object
.ForwardHelpCategory Cmdlet
#>
Instead I'll eventually add my own help content. If I didn't remove the help links, if I asked for help on Measure-MyObject, I would get help for Measure-Object, which might be confusing.
Next, my version of this tool won't be used text files, so I will delete all the parameters that are part of the TextMeasure parameter set. Instead I'll add my own parameter at the end called Format
[Parameter(ParameterSetName='GenericMeasure')]
[string]
[ValidateSet("KB","MB","GB","TB")]
${Format}
Notice I also included a validation attribute. Now the tricky part. Without getting sidetracked in the details, I need to get rid of the existing $scriptCmd variable which you'll find in the Begin script block. This is in essence the modified command that I intend to run. I'm going to leave the line but comment it out.
# $scriptCmd = {& $wrappedCmd @PSBoundParameters }
I'll be honest, writing the replacement code is the most complex part and one of the reasons I don't write a lot of proxy functions. But let's get our hands dirty.
First, because the parameter I added is not part of the original cmdlet, PowerShell will complain if you try to use it. In the Begin script block just after $wrappedCmd, I'll add an IF statement to remove the parameter from the bound parameters if it is detected.
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Measure-Object', [System.Management.Automation.CommandTypes]::Cmdlet)
# MY MODIFICATION
if ($Format) {
# REMOVE NEW PARAMETERS TO AVOID ERRORS
$PSBoundParameters.Remove('Format') | Out-Null
…
That's the easy part. The hard part is writing my own $scriptCmd expression. But here goes.
I know that if the user uses –Format and –Sum that I want to show SumX where X is the format (e.g. GB) and do not want to see the original Sum property. The same holds true for Average, and the rest. I already know I will need to customize the final output so I'll build an array of properties to exclude. Fortunately, the property names are the same as parameter names.
#define an array of property names that might need to be stripped out
#Using -IN requires PowerShell 3.0
$script:Exclude= $PSBoundParameters.Keys | Where {$_ -in @("Sum","Average","Minimum","Maximum")}
Write-Verbose "Excluding $($script:exclude -join ',') from final output"
I then need to define my divisor based on the Format parameter.
Switch ($format) {
"kb" { $script:div=1KB}
"mb" { $script:div=1MB}
"gb" { $script:div=1GB}
"tb" { $script:div=1TB}
}
Write-Verbose "Dividing by $script:div"
Finally, I can write my script command.
#the script command can only be a single pipelined expression
$scriptCmd = {
& $wrappedCmd @PSBoundParameters | foreach {
if ($_.sum) {
$_ | Add-Member -MemberType ScriptProperty -Name "Sum$($Format.ToUpper())" -Value {$this.sum/$script:div}
}
if ($_.average) {
$_ | Add-Member -MemberType ScriptProperty -Name "Average$($Format.ToUpper())" -Value {$this.average/$script:div}
}
if ($_.minimum) {
$_ | Add-Member -MemberType ScriptProperty -Name "Minimum$($Format.ToUpper())" -Value {$this.Minimum/$script:div}
}
if ($_.Maximum) {
$_ | Add-Member -MemberType ScriptProperty -Name "Maximum$($Format.ToUpper())" -Value {$this.maximum/$script:div}
}
#Write the modified object to the pipeline
Write-Output $_ | Select-Object -Property * -ExcludeProperty $script:Exclude
} #foreach
} #scriptcmd
} #if
else {
#custom parameter wasn't used so carry on as normal.
$scriptCmd = {& $wrappedCmd @PSBoundParameters }
}
Loading the function into my PowerShell session I now have a custom version of Measure-Object. Here it is in action.
Again, I could have created proxy function called Measure-Object, but my customizations would be hidden from help. Personally, I'd rather create my own tool. If you want, you can check out the finished proxy function, complete with comment-based help here.
Creating PowerShell tools can be as simple as a basic function or as complex as a proxy function. But I would say for most people it is somewhere in between. I hope you found this exploration of PowerShell toolmaking useful. If you'd like to learn more, the natural place to turn would be Learn PowerShell Toolmaking in a Month of Lunches, written by Don Jones and myself.
About the Author
Jeffery Hicks is an IT veteran with over 25 years of experience, much of it spent as an IT infrastructure consultant specializing in Microsoft server technologies with an emphasis in automation and efficiency. He is a multi-year recipient of the Microsoft MVP Award in Windows PowerShell. He works today as an independent author, trainer and consultant. Jeff has written for numerous online sites and print publications, is a contributing editor at Petri.com, and a frequent speaker at technology conferences and user groups.