添加自定义 powershell 参数属性以启用过滤

ala*_*lic 3 powershell

我正在编写一个函数,它基本上是一个它将调用的外部程序的便捷包装器。一些参数将被转发到外部程序,但不是全部。

我正在写

$ArgsToFwd = @()
switch ($PSBoundParameters.Keys) {
  '--server' {$ArgsToFwd += @('--server',$server)}
  '--userid' {$ArgsToFwd += @('--userid',$userid)}
  ...
}
Run Code Online (Sandbox Code Playgroud)

但后来我认为定义一个自定义参数属性可能会更好,它可以让我做类似的事情:

params(
[Parameter()]
[IsExternal()]
[string]$Server
)
#...
foreach ($key in $PSBoundParameters.Keys) {
    if (<#a test for the custom [IsExternal] attribute#>) {
        $ArgsToFwd += @($key, $PSBoundParameters[$key])
    }
}
Run Code Online (Sandbox Code Playgroud)

但我不太明白。可以吗?

Mat*_*sen 5

如果你想使用自定义属性,你需要做3件事:

  1. 定义自定义Attribute类型
  2. 使用所述属性的实例装饰函数的参数
  3. 编写一种机制来发现属性修饰并在运行时对它们执行某些操作

让我们从第 1 步开始,定义一个继承自 的自定义类System.Attribute

class ProxyParameterAttribute : Attribute
{
  [string]$Target

  ProxyParameterAttribute([string]$Target){
    $this.Target = $Target
  }
}
Run Code Online (Sandbox Code Playgroud)

属性注释直接映射回目标属性的构造函数,因此在本例中,我们将像使用它一样使用它[ProxyParameter('someValue')]'someValue'然后将其存储在$Target属性中。

现在我们可以继续步骤 2,用新属性装饰我们的参数。在块中应用它时,您可以省略Attribute名称的一部分param,PowerShell无论如何都希望所有注释都与属性相关:

function Invoke-SomeProgram
{
  param(
    [ProxyParameter('--server')]
    [Parameter()]
    [string]$Server
  )

  # ... code to resolve ProxyParameter goes here ...
}
Run Code Online (Sandbox Code Playgroud)

对于第 3 步,我们需要一段代码来发现当前命令参数上的属性注释,并使用它们将输入参数参数映射到适当的目标名称。

要发现当前命令声明的参数元数据,最佳入口点是$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
  }
}
Run Code Online (Sandbox Code Playgroud)

现在参数已被适当过滤和重命名,您可以通过 splatting 使用正确的参数调用目标应用程序:

.\externalApp.exe @argumentsToFwd
Run Code Online (Sandbox Code Playgroud)

把它们放在一起,你最终会得到类似的结果:

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
}
Run Code Online (Sandbox Code Playgroud)

您可以向属性添加其他属性和构造函数参数以存储更多/不同的数据(例如,指示某物是否只是一个开关或转换输入值的脚本块的标志)。

如果您需要对多个不同的命令执行此操作,请将步骤 3 的逻辑(发现属性和解析参数名称)提取到单独的函数中,或将其封装在属性类的静态方法中。