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 comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.