The other day, I posted an article about creating your own commands to simplify your life at the PowerShell prompt. Most of the time, creating your own wrapper function for an existing PowerShell command isn't too difficult. Personally, this is the approach I usually take. But PowerShell is all about building blocks and as you have seen, you can create your own. Another option is to take an existing PowerShell command and create a proxy function.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
With a proxy function you can add or remove parameters from a given command. That's what I did with Select-Object. First, you need to get the command metadata which includes things like parameters. Once you have the metadata you can create a proxy command and take it from there.
$metadata = New-Object System.Management.Automation.CommandMetaData (Get-Command Select-Object) [System.Management.Automation.ProxyCommand]::Create($metadata) | clip
I ran this command, opened a new tab in the PowerShell ISE and pasted in the new proxy command which looks like this.
[CmdletBinding(DefaultParameterSetName='DefaultParameter', HelpUri='http://go.microsoft.com/fwlink/?LinkID=113387', RemotingCapability='None')] param( [Parameter(ValueFromPipeline=$true)] [psobject] ${InputObject}, [Parameter(ParameterSetName='DefaultParameter', Position=0)] [System.Object[]] ${Property}, [Parameter(ParameterSetName='DefaultParameter')] [string[]] ${ExcludeProperty}, [Parameter(ParameterSetName='DefaultParameter')] [string] ${ExpandProperty}, [switch] ${Unique}, [Parameter(ParameterSetName='DefaultParameter')] [ValidateRange(0, 2147483647)] [int] ${Last}, [Parameter(ParameterSetName='DefaultParameter')] [ValidateRange(0, 2147483647)] [int] ${First}, [Parameter(ParameterSetName='DefaultParameter')] [ValidateRange(0, 2147483647)] [int] ${Skip}, [switch] ${Wait}, [Parameter(ParameterSetName='IndexParameter')] [ValidateRange(0, 2147483647)] [int[]] ${Index}) begin { try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Select-Object', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } process { try { $steppablePipeline.Process($_) } catch { throw } } end { try { $steppablePipeline.End() } catch { throw } } <# .ForwardHelpTargetName Select-Object .ForwardHelpCategory Cmdlet #>
Because I knew I wanted to create my own command with my own help, I deleted the references to help and wrapped the command in a Function. In this situation, I wanted to get rid of all parameters except InputObject and Last. Everything else remains the same. What will happen is that when I run the function it will invoke the original command in a steppable pipeline. Don't worry too much about that. Here's my finished proxy function.
#requires -version 3.0 #this is a proxy function version of Select-Object Function Select-Last { <# .Synopsis Select the last X number of objects. .Description This is a proxy version of Select-Object designed to select the last X number of objects. .Example PS C:\> 1..1000 | select-last 5 996 997 998 999 1000 .Notes Last Updated: 8/26/2014 Version : 0.9 .Link Select-Object #> [CmdletBinding(DefaultParameterSetName='DefaultParameter', RemotingCapability='None')] param( [Parameter(ValueFromPipeline=$true)] [psobject]$InputObject, [Parameter(ParameterSetName='DefaultParameter', Position=0,Mandatory=$True, HelpMessage="How many items do you want to select?")] [ValidateRange(0, 2147483647)] [int]$Last ) begin { try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Select-Object', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } process { try { $steppablePipeline.Process($_) } catch { throw } } end { try { $steppablePipeline.End() } catch { throw } } } #end function
I made the Last parameter mandatory and positional. Now I can run a command like this:
get-eventlog system | select-last 3
It would be even better if I define my Last alias to Select-Last. Performance-wise, this performs somewhere in-between my wrapper function and the original command. Of course, I need a Select-First, so here's the proxy version for that:
#requires -version 3.0 #this is a proxy function version of Select-Object Function Select-First { <# .Synopsis Select the first X number of objects. .Description This is a proxy version of Select-Object designed to select the first X number of objects. .Example PS C:\> 1..1000 | select-first 5 1 2 3 4 5 .Notes Last Updated: 8/26/2014 Version : 0.9 .Link Select-Object #> [CmdletBinding(DefaultParameterSetName='DefaultParameter', RemotingCapability='None')] param( [Parameter(ValueFromPipeline=$true)] [psobject]$InputObject, [Parameter(ParameterSetName='DefaultParameter', Position=0,Mandatory=$True, HelpMessage="How many items do you want to select?")] [ValidateRange(0, 2147483647)] [int]$First ) begin { try { $outBuffer = $null if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { $PSBoundParameters['OutBuffer'] = 1 } $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Select-Object', [System.Management.Automation.CommandTypes]::Cmdlet) $scriptCmd = {& $wrappedCmd @PSBoundParameters } $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) $steppablePipeline.Begin($PSCmdlet) } catch { throw } } process { try { $steppablePipeline.Process($_) } catch { throw } } end { try { $steppablePipeline.End() } catch { throw } } } #end function
The concept of a proxy command is to give a user a stripped down version of a known PowerShell command, but in this case I'm using a proxy command as an alternative primarily so I can be lazy and create aliases. With a little work you can create a custom toolset to meet your needs at the PowerShell prompt and be as lazy, I mean efficient, as you want.
Excellent article, especially as a follow up to yesterday’s.
Excellent. concise, complete and ready to use.
Hi Jeffery!
Jeffrey Snover has Posted a very good Blog Post about Proxy Functions (Proxy Commands)
see: http://blogs.msdn.com/b/powershell/archive/2009/01/04/extending-and-or-modifing-commands-with-proxies.aspx
I have picked up his work and have improved this with new features.
Add or remove parameters to/from origin command
Comment based help is added by default
Insert sourcecode automatically into PowerShell ISE
create a command with a new name instead of a shadowing proxy function
See:
New-ProxyCommand a simple way to create a PowerShell Proxy Function
http://gallery.technet.microsoft.com/scriptcenter/New-ProxyCommand-a-simple-9735745e
greets Peter Kriegel
founder member of the German speaking PowerShell Community
http://www.PowerShell-Group.eu
Be careful with Select-First and Select-Last because of this bug:
https://connect.microsoft.com/PowerShell/Feedback/Details/810996
e.g.
1..40 | Select-Object -First 3 |end {‘yup’} }
1..40 | Select-First 3 |end {‘nope’} }