I'm writing a function that's basically a convenience wrapper around an external program that it'll invoke. Several of the parameters will be forwarded to the external program, but not all.
I was writing
$ArgsToFwd = @()
switch ($PSBoundParameters.Keys) {
'--server' {$ArgsToFwd += @('--server',$server)}
'--userid' {$ArgsToFwd += @('--userid',$userid)}
...
}
but then I thought it might be better to define a custom parameter attribute that could let me do something like:
params(
[Parameter()]
[IsExternal()]
[string]$Server
)
#...
foreach ($key in $PSBoundParameters.Keys) {
if (<#a test for the custom [IsExternal] attribute#>) {
$ArgsToFwd += @($key, $PSBoundParameters[$key])
}
}
But I can't quite figure it out. Can that be done?
If you want to use custom attributes, there are 3 things you need to do:
Attribute typeLet'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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With