PoshCode Logo PowerShell Code Repository

New-CommandWrapper.ps1 by Lee Holmes 3 years ago
embed code: <script type="text/javascript" src="http://PoshCode.org/embed/2197"></script>download | new post

From Windows PowerShell Cookbook (O’Reilly) by Lee Holmes

  1. ##############################################################################
  2. ##
  3. ## New-CommandWrapper
  4. ##
  5. ## From Windows PowerShell Cookbook (O'Reilly)
  6. ## by Lee Holmes (http://www.leeholmes.com/guide)
  7. ##
  8. ##############################################################################
  9.  
  10. <#
  11.  
  12. .SYNOPSIS
  13.  
  14. Adds parameters and functionality to existing cmdlets and functions.
  15.  
  16. .EXAMPLE
  17.  
  18. New-CommandWrapper Get-Process `
  19.       -AddParameter @{
  20.           SortBy = {
  21.               $newPipeline = {
  22.                   __ORIGINAL_COMMAND__ | Sort-Object -Property $SortBy
  23.               }
  24.           }
  25.       }
  26.  
  27. This example adds a 'SortBy' parameter to Get-Process. It accomplishes
  28. this by adding a Sort-Object command to the pipeline.
  29.  
  30. .EXAMPLE
  31.  
  32. $parameterAttributes = @'
  33.           [Parameter(Mandatory = $true)]
  34.           [ValidateRange(50,75)]
  35.           [Int]
  36. '@
  37.  
  38. New-CommandWrapper Clear-Host `
  39.       -AddParameter @{
  40.           @{
  41.               Name = 'MyMandatoryInt';
  42.               Attributes = $parameterAttributes
  43.           } = {
  44.               Write-Host $MyMandatoryInt
  45.               Read-Host "Press ENTER"
  46.          }
  47.       }
  48.  
  49. This example adds a new mandatory 'MyMandatoryInt' parameter to
  50. Clear-Host. This parameter is also validated to fall within the range
  51. of 50 to 75. It doesn't alter the pipeline, but does display some
  52. information on the screen before processing the original pipeline.
  53.  
  54. #>
  55.  
  56. param(
  57.     ## The name of the command to extend
  58.     [Parameter(Mandatory = $true)]
  59.     $Name,
  60.  
  61.     ## Script to invoke before the command begins
  62.     [ScriptBlock] $Begin,
  63.  
  64.     ## Script to invoke for each input element
  65.     [ScriptBlock] $Process,
  66.  
  67.     ## Script to invoke at the end of the command
  68.     [ScriptBlock] $End,
  69.  
  70.     ## Parameters to add, and their functionality.
  71.     ##
  72.     ## The Key of the hashtable can be either a simple parameter name,
  73.     ## or a more advanced parameter description.
  74.     ##
  75.     ## If you want to add additional parameter validation (such as a
  76.     ## parameter type,) then the key can itself be a hashtable with the keys
  77.     ## 'Name' and 'Attributes'. 'Attributes' is the text you would use when
  78.     ## defining this parameter as part of a function.
  79.     ##
  80.     ## The Value of each hashtable entry is a scriptblock to invoke
  81.     ## when this parameter is selected. To customize the pipeline,
  82.     ## assign a new scriptblock to the $newPipeline variable. Use the
  83.     ## special text, __ORIGINAL_COMMAND__, to represent the original
  84.     ## command. The $targetParameters variable represents a hashtable
  85.     ## containing the parameters that will be passed to the original
  86.     ## command.
  87.     [HashTable] $AddParameter
  88. )
  89.  
  90. Set-StrictMode -Version Latest
  91.  
  92. ## Store the target command we are wrapping, and its command type
  93. $target = $Name
  94. $commandType = "Cmdlet"
  95.  
  96. ## If a function already exists with this name (perhaps it's already been
  97. ## wrapped,) rename the other function and chain to its new name.
  98. if(Test-Path function:\$Name)
  99. {
  100.     $target = "$Name" + "-" + [Guid]::NewGuid().ToString().Replace("-","")
  101.     Rename-Item function:\GLOBAL:$Name GLOBAL:$target
  102.     $commandType = "Function"
  103. }
  104.  
  105. ## The template we use for generating a command proxy
  106. $proxy = @'
  107.  
  108. __CMDLET_BINDING_ATTRIBUTE__
  109. param(
  110. __PARAMETERS__
  111. )
  112. begin
  113. {
  114.    try {
  115.        __CUSTOM_BEGIN__
  116.  
  117.        ## Access the REAL Foreach-Object command, so that command
  118.        ## wrappers do not interfere with this script
  119.        $foreachObject = $executionContext.InvokeCommand.GetCmdlet(
  120.            "Microsoft.PowerShell.Core\Foreach-Object")
  121.  
  122.        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand(
  123.            '__COMMAND_NAME__',
  124.            [System.Management.Automation.CommandTypes]::__COMMAND_TYPE__)
  125.  
  126.        ## TargetParameters represents the hashtable of parameters that
  127.        ## we will pass along to the wrapped command
  128.        $targetParameters = @{}
  129.        $PSBoundParameters.GetEnumerator() |
  130.            & $foreachObject {
  131.                if($command.Parameters.ContainsKey($_.Key))
  132.                {
  133.                    $targetParameters.Add($_.Key, $_.Value)
  134.                }
  135.            }
  136.  
  137.        ## finalPipeline represents the pipeline we wil ultimately run
  138.        $newPipeline = { & $wrappedCmd @targetParameters }
  139.        $finalPipeline = $newPipeline.ToString()
  140.  
  141.        __CUSTOM_PARAMETER_PROCESSING__
  142.  
  143.        $steppablePipeline = [ScriptBlock]::Create(
  144.            $finalPipeline).GetSteppablePipeline()
  145.        $steppablePipeline.Begin($PSCmdlet)
  146.    } catch {
  147.        throw
  148.    }
  149. }
  150.  
  151. process
  152. {
  153.    try {
  154.        __CUSTOM_PROCESS__
  155.        $steppablePipeline.Process($_)
  156.    } catch {
  157.        throw
  158.    }
  159. }
  160.  
  161. end
  162. {
  163.    try {
  164.        __CUSTOM_END__
  165.        $steppablePipeline.End()
  166.    } catch {
  167.        throw
  168.    }
  169. }
  170.  
  171. dynamicparam
  172. {
  173.    ## Access the REAL Get-Command, Foreach-Object, and Where-Object
  174.    ## commands, so that command wrappers do not interfere with this script
  175.    $getCommand = $executionContext.InvokeCommand.GetCmdlet(
  176.        "Microsoft.PowerShell.Core\Get-Command")
  177.    $foreachObject = $executionContext.InvokeCommand.GetCmdlet(
  178.        "Microsoft.PowerShell.Core\Foreach-Object")
  179.    $whereObject = $executionContext.InvokeCommand.GetCmdlet(
  180.        "Microsoft.PowerShell.Core\Where-Object")
  181.  
  182.    ## Find the parameters of the original command, and remove everything
  183.    ## else from the bound parameter list so we hide parameters the wrapped
  184.    ## command does not recognize.
  185.    $command = & $getCommand __COMMAND_NAME__ -Type __COMMAND_TYPE__
  186.    $targetParameters = @{}
  187.    $PSBoundParameters.GetEnumerator() |
  188.        & $foreachObject {
  189.            if($command.Parameters.ContainsKey($_.Key))
  190.            {
  191.                $targetParameters.Add($_.Key, $_.Value)
  192.            }
  193.        }
  194.  
  195.    ## Get the argumment list as it would be passed to the target command
  196.    $argList = @($targetParameters.GetEnumerator() |
  197.        Foreach-Object { "-$($_.Key)"; $_.Value })
  198.  
  199.    ## Get the dynamic parameters of the wrapped command, based on the
  200.    ## arguments to this command
  201.    $command = $null
  202.    try
  203.    {
  204.        $command = & $getCommand __COMMAND_NAME__ -Type __COMMAND_TYPE__ `
  205.            -ArgumentList $argList
  206.    }
  207.    catch
  208.    {
  209.  
  210.    }
  211.  
  212.    $dynamicParams = @($command.Parameters.GetEnumerator() |
  213.        & $whereObject { $_.Value.IsDynamic })
  214.  
  215.    ## For each of the dynamic parameters, add them to the dynamic
  216.    ## parameters that we return.
  217.    if ($dynamicParams.Length -gt 0)
  218.    {
  219.        $paramDictionary = `
  220.            New-Object Management.Automation.RuntimeDefinedParameterDictionary
  221.        foreach ($param in $dynamicParams)
  222.        {
  223.            $param = $param.Value
  224.            $arguments = $param.Name, $param.ParameterType, $param.Attributes
  225.            $newParameter = `
  226.                New-Object Management.Automation.RuntimeDefinedParameter `
  227.                $arguments
  228.            $paramDictionary.Add($param.Name, $newParameter)
  229.        }
  230.        return $paramDictionary
  231.    }
  232. }
  233.  
  234. <#
  235.  
  236. .ForwardHelpTargetName __COMMAND_NAME__
  237. .ForwardHelpCategory __COMMAND_TYPE__
  238.  
  239. #>
  240.  
  241. '@
  242.  
  243. ## Get the information about the original command
  244. $originalCommand = Get-Command $target
  245. $metaData = New-Object System.Management.Automation.CommandMetaData `
  246.     $originalCommand
  247. $proxyCommandType = [System.Management.Automation.ProxyCommand]
  248.  
  249. ## Generate the cmdlet binding attribute, and replace information
  250. ## about the target
  251. $proxy = $proxy.Replace("__CMDLET_BINDING_ATTRIBUTE__",
  252.     $proxyCommandType::GetCmdletBindingAttribute($metaData))
  253. $proxy = $proxy.Replace("__COMMAND_NAME__", $target)
  254. $proxy = $proxy.Replace("__COMMAND_TYPE__", $commandType)
  255.  
  256. ## Stores new text we'll be putting in the param() block
  257. $newParamBlockCode = ""
  258.  
  259. ## Stores new text we'll be putting in the begin block
  260. ## (mostly due to parameter processing)
  261. $beginAdditions = ""
  262.  
  263. ## If the user wants to add a parameter
  264. $currentParameter = $originalCommand.Parameters.Count
  265. if($AddParameter)
  266. {
  267.     foreach($parameter in $AddParameter.Keys)
  268.     {
  269.         ## Get the code associated with this parameter
  270.         $parameterCode = $AddParameter[$parameter]
  271.  
  272.         ## If it's an advanced parameter declaration, the hashtable
  273.         ## holds the validation and / or type restrictions
  274.         if($parameter -is [Hashtable])
  275.         {
  276.             ## Add their attributes and other information to
  277.             ## the variable holding the parameter block additions
  278.             if($currentParameter -gt 0)
  279.             {
  280.                 $newParamBlockCode += ","
  281.             }
  282.  
  283.             $newParamBlockCode += "`n`n    " +
  284.                 $parameter.Attributes + "`n" +
  285.                 '    $' + $parameter.Name
  286.  
  287.             $parameter = $parameter.Name
  288.         }
  289.         else
  290.         {
  291.             ## If this is a simple parameter name, add it to the list of
  292.             ## parameters. The proxy generation APIs will take care of
  293.             ## adding it to the param() block.
  294.             $newParameter =
  295.                 New-Object System.Management.Automation.ParameterMetadata `
  296.                     $parameter
  297.             $metaData.Parameters.Add($parameter, $newParameter)
  298.         }
  299.  
  300.         $parameterCode = $parameterCode.ToString()
  301.  
  302.         ## Create the template code that invokes their parameter code if
  303.         ## the parameter is selected.
  304.         $templateCode = @"
  305.  
  306.        if(`$PSBoundParameters['$parameter'])
  307.        {
  308.            $parameterCode
  309.  
  310.            ## Replace the __ORIGINAL_COMMAND__ tag with the code
  311.            ## that represents the original command
  312.            `$alteredPipeline = `$newPipeline.ToString()
  313.            `$finalPipeline = `$alteredPipeline.Replace(
  314.                '__ORIGINAL_COMMAND__', `$finalPipeline)
  315.        }
  316. "@
  317.  
  318.         ## Add the template code to the list of changes we're making
  319.         ## to the begin() section.
  320.         $beginAdditions += $templateCode
  321.         $currentParameter++
  322.     }
  323. }
  324.  
  325. ## Generate the param() block
  326. $parameters = $proxyCommandType::GetParamBlock($metaData)
  327. if($newParamBlockCode) { $parameters += $newParamBlockCode }
  328. $proxy = $proxy.Replace('__PARAMETERS__', $parameters)
  329.  
  330. ## Update the begin, process, and end sections
  331. $proxy = $proxy.Replace('__CUSTOM_BEGIN__', $Begin)
  332. $proxy = $proxy.Replace('__CUSTOM_PARAMETER_PROCESSING__', $beginAdditions)
  333. $proxy = $proxy.Replace('__CUSTOM_PROCESS__', $Process)
  334. $proxy = $proxy.Replace('__CUSTOM_END__', $End)
  335.  
  336. ## Save the function wrapper
  337. Write-Verbose $proxy
  338. Set-Content function:\GLOBAL:$NAME $proxy
  339.  
  340. ## If we were wrapping a cmdlet, hide it so that it doesn't conflict with
  341. ## Get-Help and Get-Command
  342. if($commandType -eq "Cmdlet")
  343. {
  344.     $originalCommand.Visibility = "Private"
  345. }

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me