[HOWTO] Automatically applying PSDefaultValue to C# based PSCmdlet Parameters

One of the cool features of PowerShell script based Cmdlets is the possibility to define default values for Cmdlet parameters. This is typically done like this:

PARAM
(
  [Parameter(Mandatory = $false)]
  [string] $Name = 'my default value'
)

Whenever we invoke such a Cmdlet without specifying the Name parameter the PowerShell runtime will insert the value my default value into that parameter.

C# based PSCmdlet provide a similar approach to this by providing a PSDefaultValue attribute to specify a default value:

[Parameter(Mandatory = false)]
[PSDefaultValue(Value = 'my default value')]
public string Name { get; set; }

However the value of such an annotation will NOT be automatically applied to the Name property when not specified by the caller.

Of course we can work around this by defining a private backing field:

private string name = 'my default value';
[Parameter(Mandatory = false)]
[PSDefaultValue(Value = 'my default value')]
public string Name 
{ 
  get
  {
    return name;
  }
  set
  {
    name = value;
  }
}

This certainly does not really improve readability. and of course this is not necessary when using C# 6.0 where you can write it like this:

[Parameter(Mandatory = false)]
[PSDefaultValue(Value = 'my default value')]
public string Name { get; set; } = 'my default value';

But if you are still using C# 5.0 you can utilise the BeginProcessing virtual method and apply the default values via reflection

public class PsCmdletBase : PSCmdlet
{
  protected override void BeginProcessing()
  {
    base.BeginProcessing();

    SetDefaultValues();
  }

  protected virtual void SetDefaultValues()
  {
    var propertyInfos = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance)
      .Where
      (
        propertyInfo => 
        !MyInvocation.BoundParameters.ContainsKey(propertyInfo.Name) 
        &&
        Attribute.GetCustomAttributes(propertyInfo, typeof(ParameterAttribute)).Any()
      );

    foreach (var propertyInfo in propertyInfos)
    {
      // only process Parameter with a PSDefaultValue
      var psDefaultValueAttribute = (PSDefaultValueAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PSDefaultValueAttribute));
      if (null == psDefaultValueAttribute)
      {
        continue;
      }

      propertyInfo.SetValue(this, psDefaultValueAttribute.Value, null);
    }
  }
}

As you can see we only apply the default values to parameters that are not explicitly specified by the caller (MyInvocation.BoundParameters.ContainsKey). Now all we have to do is to derive from our new base class and have its default values applied:

public class MyCmdlet : PsCmdletBase
{
  [Parameter(Mandatory = false)]
  [PSDefaultValue(Value = 'my default value')]
  public string Name { get; set; }
}

Note: it is not possible to process this in a default constructor as the BoundParameters dictionary is not initialised at that point in time.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: