Extending and/or Modifing Commands with Proxies

Extending and/or Modifing Commands with Proxies

  • Comments 4

There are so many powerful features in V2, it is hard to know where to begin.  This one is going to blow to top of your head off when you understand what it enables you to do.

In this blog, I talk about Proxy Cmdlets which is the ability for one Cmdlet to call another.  You could always do this but to do it right has always been very difficult.  In particular, what you want to have happen is to be able to control the execution of the calling command - to control when it's BEGINPROCESS(), PROCESSRECORD(), ENDPROCESS(), etc methods are called so that it behave with the correct behavior.  What you needed was a "Steppable Pipeline".  You know have that in CTP3.  Now the bit-biters will want to explore steppable pipelines to great detail as they can be both powerful and complex - BUT the thing I want to make clear is that EVERYONE can and should use them to great effect by simply following the recipe I'm going to provide you in this blog and with the attached Module.

 

Scenario 1:  Removing Functions
Image the case where you are Configuring PowerShell for Remoting and you are setting up an ConfigurationName which will give people access to some but not all commands.  You might want to also further restrict the commands  you do provide. 

 

Scenario 2: Adding Functions
In V2 CTP3 we added the -FileVersionInfo and -Module parameters to Get-Process (did you know that?  Stop a minute and go give them a try - they are pretty cool/useful.  try this:  gps powershell -FileVersionInfo |fl * ).  With proxies - you could do this yourself very easily.

 

Concept
Let's take a minute and get things into focus.  PowerShell Cmdlets are .NET classes with attributes on the properties.  WHY?  Because that allows us to reflect on them and EXTRACT ITS METADATA (its grammar).  We then use that grammar to drive a command command line syntax.  This is the magic behind PowerShell.  You'll see us do all sorts of wonderful things with this in the future (e.g. early-bound WebService interface, emit earlybound C# accessors, autogenerate GUI front ends, etc., etc.).  The big things we did in V2 is

1) We expose the metadata.  You can do a New-Object on System.Management.Automation.CommandMetaData passing it cmdletInfo and get it's metadata.  Try this:
    PS> New-Object System.Management.Automation.CommandMetaData (gcm Get-Process)

2) We make the metadata programmable.  You can add/remove parameters, change the parameters, change the name, etc.

3) We use metadata to emit a script Cmdlet.
    PS> $metaData = New-Object System.Management.Automation.CommandMetaData (gcm Get-Process)
    PS> [System.Management.Automation.ProxyCommand]::create($MetaData)

 

 

Developers and advanced scripters are going to be all over that like white on rice but that can get to be chewy stuff for novice scripters.  Does that mean that Novice scripters won't use this?  ABSOLUTELY NOT!  With PowerShell, people produce abstractions to enable other groups of people.  Advanced scripters often produce abstractions which enable lower skilled scripters to do things they would not have been able to do on their own.  That is what this blog is about.  I've attached a MetaProgramming Module which (hopefully) make this a pretty simple task.

PS> Import-Module MetaProgramming

PS> Get-Command -Module MetaProgramming

CommandType     Name                          Definition
-----------     ----                          ----------
Function        New-ParameterAttribute        ...
Function        New-ProxyCommand              ...


PS> Get-Help New-ProxyCommand -Detailed

NAME
    New-ProxyCommand

SYNOPSIS
    Generate a script for a ProxyCommand to call a base Cmdlet adding or re
    moving parameters.

SYNTAX
    New-ProxyCommand [[-Name] [<String>]] [[-CommandType] [<CommandTypes>]]
     [[-AddParameter] [<String[]>]] [[-RemoveParameter] [<String[]>]] [[-Ad
    dParameterAttribute] [<Hashtable>]] [<CommonParameters>]

DETAILED DESCRIPTION
    This command generates command which calls another command (a ProxyComm
    and).
    In doing so, it can add additional attributes to the existing parameter
    s.
    This is useful for things like enforcing corporate naming standards.
    It can also ADD or REMOVE parameters.  If you ADD a parameter, you'll h
    ave
    to implement the semantics of that parameter in the code that gets gene
    rated.

PARAMETERS
    -Name
        Name of the Cmdlet to proxy.

    -CommandType
        Type of Command we are proxying.  In general you dont' need to spec
        ify this but
        it becomes necessary if there is both a cmdlet and a function with
         the same
        name

    -AddParameter
        List of Parameters you would like to add. NOTE - you have to edit t
        he resultant
        code to implement the semantics of these parameters.  ALSO - you ne
        ed to remove
        them from $PSBOUND

    -RemoveParameter

    -AddParameterAttribute

    <CommonParameters>
        This cmdlet supports the common parameters: -Verbose, -Debug,
        -ErrorAction, -ErrorVariable, -WarningAction, -WarningVariable,
        -OutBuffer and -OutVariable. For more information, type,
        "get-help about_commonparameters".

    -------------------------- EXAMPLE 1 --------------------------

    New-ProxyCommand get-process -typ all -RemoveParameter FileVersionInfo,
    Module,ComputerName -AddParameter SortBy > c:\ps\get-myprocess.ps1
REMARKS
    To see the examples, type: "get-help New-ProxyCommand -examples".
    For more information, type: "get-help New-ProxyCommand -detailed".
    For technical information, type: "get-help New-ProxyCommand -full".

 

 

So let's try a simple example to see how easy it can be:

PS>New-ProxyCommand get-process -RemoveParameter ID > .\get-myprocess.ps1
PS>.\Get-MyProcess -Name lsass

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
   1126      17     5796      11744    37    97.64    488 lsass

PS>.\Get-MyProcess -ID 488
C:\temp\get-myprocess.ps1 : A parameter cannot be found that matches parame
ter name 'ID'.
At line:1 char:20
+ .\Get-MyProcess -ID <<<<  488
    + CategoryInfo          : InvalidArgument: (:) [get-myprocess.ps1], Pa
   rameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,get-myprocess.ps1

 

 

Adding parameter is a bit more complicated but if you follow the steps - it is pretty straightforward (It might look intimidating but trust me - just do it a couple times and you'll see that is is super simple.).  How do you know what the steps are?  I put it into the script.  If you the following command:

PS> New-ProxyCommand get-process -AddParameter SortBy > .\get-myprocess.ps1
PS> Powershell_ise .\get-myprocess.ps1

This is what you'll see at the top of that file:

<#
You are responsible for implementing the logic for added parameters.  These
parameters are bound to $PSBoundParameters so if you pass them on the the
command you are proxying, it will almost certainly cause an error.  This logic
should be added to your BEGIN statement to remove any specified parameters
from $PSBoundParameters.

In general, the way you are going to implement additional parameters is by
modifying the way you generate the $scriptCmd variable.  Here is an example
of how you would add a -SORTBY parameter to a cmdlet:

        if ($SortBy)
        {
            [Void]$PSBoundParameters.Remove("SortBy")
            $scriptCmd = {& $wrappedCmd @PSBoundParameters |Sort-Object -Property $SortBy}
        }else
        {
            $scriptCmd = {& $wrappedCmd @PSBoundParameters }
        }
################################################################################       
New ATTRIBUTES
        if ($SortBy)
        {
            [Void]$PSBoundParameters.Remove("SortBy")
        }

################################################################################
#>

 

 

NOTE - The New-ProxyCommand code should emit a HELP template for the proxy but I have not had time to go implement that.  Sorry.

Enjoy!

Jeffrey Snover [MSFT]
Windows Management Partner Architect
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

Attachment: MetaProgramming.zip
Leave a Comment
  • Please add 2 and 4 and type the answer here:
  • Post
  • WOW.

    Just as I was thinking I understood stuff...

    My head is exploding - I need to go lie down...

    ER - VERY cool stuff.

  • Pretty freakin' amazing stuff.

    Looks like we have a work-around for adding some switches -ContainersOnly and -NoContainersOnly to the bug/suggestion on Connect about get-childItem.

    I posted a version up on PoshCode at http://poshcode.org/785

  • I got some of the samples working, eample http://poshcode.org/1261

    but what I wish is to wrap Select-String, so that it behaves like

    # $pattern = 'Ärger'

    @(Select-String * -pattern $pattern -encoding default) +

    @(Select-String * -pattern $pattern -encoding oem) |sort-object | Get-Unique

    I want it to find in unicode, ansi and oem files.

    I found no way to call the wraped command twice with different parameters. Is it posible ?

  • Ultimate !!!

Page 1 of 1 (4 items)