Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Making the Shell Work for You Revisited

Posted on September 3, 2014September 2, 2014

blocks 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.

Manage and Report Active Directory, Exchange and Microsoft 365 with
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.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

4 thoughts on “Making the Shell Work for You Revisited”

  1. mikeshepard says:
    September 3, 2014 at 10:16 am

    Excellent article, especially as a follow up to yesterday’s.

  2. jvierra says:
    September 4, 2014 at 7:02 am

    Excellent. concise, complete and ready to use.

  3. Peter Kriegel says:
    September 20, 2014 at 8:49 am

    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

  4. Derp McDerp says:
    October 16, 2014 at 11:13 pm

    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’} }

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d