Add custom powershell parameter attribute to enable filtering
Solution 1:
If you want to use custom attributes, there are 3 things you need to do:
- Define a custom
Attribute
type - Decorate your function's parameters with instances of said attribute
- Write a mechanism to discover the attribute decorations and do something with them at runtime
Let's start with step 1, by defining a custom class that inherits from System.Attribute
:
class ProxyParameterAttribute : Attribute
{
[string]$Target
ProxyParameterAttribute([string]$Target){
$this.Target = $Target
}
}
Attribute annotations map directly back to the target attribute's constructor, so in this case, we'll be using it like [ProxyParameter('someValue')]
and 'someValue'
will then be stored in the $Target
property.
Now we can move on to step 2, decorating our parameters with the new attribute. You can omit the Attribute
part of the name when applying it in the param
block, PowerShell is expecting all annotations to be attribute-related anyway:
function Invoke-SomeProgram
{
param(
[ProxyParameter('--server')]
[Parameter()]
[string]$Server
)
# ... code to resolve ProxyParameter goes here ...
}
For step 3, we need a piece of code that can discover the attribute annotations on the parameters of the current command, and use them to map the input parameter arguments to the appropriate target names.
To discover declared parameter metadata for the current command, the best entry point is $MyInvocation
:
# Create an array to hold all the parameters you want to forward
$argumentsToFwd = @()
# Create mapping table for parameter names
$paramNameMapping = @{}
# Discover current command
$currentCommandInfo = $MyInvocation.MyCommand
# loop through all parameters, populate mapping table with target param names
foreach($param in $currentCommandInfo.Parameters.GetEnumerator()){
# attempt to discover any [ProxyParameter] attribute decorations
$proxyParamAttribute = $param.Value.Attributes.Where({$_.TypeId -eq [ProxyParamAttribute]}, 'First') |Select -First 1
if($proxyParamAttribute){
$paramNameMapping[$param.Name] = $proxyParamAttribute.Target
}
}
# now loop over all parameter arguments that were actually passed by the caller, populate argument array while taking ProxyParameter mapping into account
foreach($boundParam in $PSBoundParameters.GetEnumerator()){
$name = $boundParam.Name
$value = $boundParam.Value
if($paramNameMapping.ContainsKey[$name]){
$argumentsToFwd += $paramNameMapping[$name],$value
}
}
Now that the parameters how been filtered and renamed appropriately, you can invoke the target application with the correct arguments via splatting:
.\externalApp.exe @argumentsToFwd
Putting it all together, you end up with something like:
class ProxyParameterAttribute : Attribute
{
[string]$Target
ProxyParameterAttribute([string]$Target){
$this.Target = $Target
}
}
function Invoke-SomeProgram
{
param(
[ProxyParameter('--server')]
[Parameter()]
[string]$Server
)
# Create an array to hold all the parameters you want to forward
$argumentsToFwd = @()
# Create mapping table for parameter names
$paramNameMapping = @{}
# Discover current command
$currentCommandInfo = $MyInvocation.MyCommand
# loop through all parameters, populate mapping table with target param names
foreach($param in $currentCommandInfo.Parameters.GetEnumerator()){
# attempt to discover any [ProxyParameter] attribute decorations
$proxyParamAttribute = $param.Value.Attributes.Where({$_.TypeId -eq [ProxyParamAttribute]}, 'First') |Select -First 1
if($proxyParamAttribute){
$paramNameMapping[$param.Name] = $proxyParamAttribute.Target
}
}
# now loop over all parameter arguments that were actually passed by the caller, populate argument array while taking ProxyParameter mapping into account
foreach($boundParam in $PSBoundParameters.GetEnumerator()){
$name = $boundParam.Name
$value = $boundParam.Value
if($paramNameMapping.ContainsKey[$name]){
$argumentsToFwd += $paramNameMapping[$name],$value
}
}
externalApp.exe @argumentsToFwd
}
You can add other properties and constructor arguments to the attributes to store more/different data (a flag to indicate whether something is just a switch, or a scriptblock that transforms the input value for example).
If you need this for multiple different commands, extract the step 3 logic (discovering attributes and resolving parameter names) to a separate function or encapsulate it in a static method on the attribute class.