PowerShell / PSDesiredStateConfiguration

Source for https://www.powershellgallery.com/packages/PSDesiredStateConfiguration module
MIT License
58 stars 14 forks source link

Support distinguishing between default and explicit values for value type properties #116

Open michaeltlombardi opened 1 year ago

michaeltlombardi commented 1 year ago

Summary of the new feature / enhancement

As a DSC Resource author writing resources as PowerShell classes, I want to be able to distinguish between whether a [value type][01] property has been explicitly set or is the default value. For PowerShell classes, [reference type][02] properties, like strings and complex properties (objects), the property value is $null when not explicitly set.

For value type properties, like booleans and integers, the property has a default value when the class is initialized. In the Test() and Set() methods for a resource, the resource can't distinguish between whether the property is its default value (and should be ignored for optional properties) or was explicitly set (and should be used).

The only workarounds for this are:

  1. Make all value-type properties mandatory properties.
  2. Give all value-type properties a semantically definitive default value.

1 - Mandatory value-type properties

The first option artificially limits how a resource can manage a component in ways that may not map to the reality of configuration for that component. Especially if a resource has more than one or two value-type properties.

2 - Default values for value-type properties

The second option works okay for numeric types that can't be set to 0, like the weeks interval for scheduled tasks. Because numerics default to 0, you can be sure that a 0 value indicates the value wasn't explicitly set (and throw an error on invalid value if a user does try to set it).

For enums, this option can work okay. It's common for the $Ensure property. However, if the enum doesn't have a default value in the managed component, then the definition needs to be something like:

enum MyKind {
  Undefined
  Foo
  Bar
  Baz
}

[DscResource()]
class MyResource {
  [DscProperty()] [MyKind] [ValidateSet(
    'Foo',
    'Bar',
    'Baz'
  )] $Kind

  [DscProperty(Key)] [string] $Path

  [MyResource] Get()  { return [MyResource]::new() }
  [bool]       Test() { return $true }
  [void]       Set()  { }
}

That's not intuitive to define and leads to this error message when an invalid value is specified:

$a = [MyResource]::new()
$a.Kind = 'Undefined'
SetValueInvocationException:
  Exception setting "Kind":
    "The argument "Undefined" does not belong to the set "Foo,Bar,Baz" specified by the
    ValidateSet attribute. Supply an argument that is in the set and then try the command
    again."

Compared to the message for a normal enum:

enum MyKindB {
  Foo
  Bar
  Baz
}

[DscResource()]
class MyResourceB {
  [DscProperty()] [MyKindB] $Kind

  [DscProperty(Key)] [string] $Path

  [MyResourceB] Get()  { return [MyResourceB]::new() }
  [bool]       Test() { return $true }
  [void]       Set()  { }
}
$b = [MyResourceB]::new()
$b.Kind = 'Undefined'
SetValueInvocationException:
  Exception setting "Kind": "Cannot convert value "Undefined" to type "MyKindB".
    Error: "Unable to match the identifier name Undefined to a valid enumerator name.
      Specify one of the following enumerator names and try again:
        Foo, Bar, Baz""

Summary

Option 1 forces the resource to model the component in a way that might not match reality and may involve long lists of mandatory properties. Option 2 only sometimes works. It always requires the author to keep their implementation up-to-date with the default values for the component, possibly supporting defaults on a per-version basis, and likely requires additional custom handling and validation.

If a resource could tell whether value-type properties were explicitly set, no workarounds would be needed.

Proposed technical implementation details (optional)

The only implementation I can think of that could help with this without requiring substantial work in PowerShell to support accessors/mutators for class properties (see: PowerShell/PowerShell#2219) is for the PSDesiredStateConfiguration module to define an interface for DSC Resources (#81) with a default implementation (#82) that includes this functionality.