Skip to content

Microsoft.Windows/FirewallRuleList: unspecifiedRulesAction is always reported as drift, so test and set never converge #1666

Description

@boaz-raz

Steps to reproduce

Tested on dsc 3.3.0-preview.4, Microsoft.Windows/FirewallRuleList v0.2.0, Windows Server 2025, elevated.

Create a rule, then declare it exactly as it exists so there is genuinely no drift:

New-NetFirewallRule -Name 'DSC-Repro' -DisplayName 'DSC-Repro' -Direction Inbound `
  -Action Allow -Protocol TCP -LocalPort 32921 -Enabled True -PolicyStore PersistentStore
# repro.dsc.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: fw
  type: Microsoft.Windows/FirewallRuleList
  properties:
    unspecifiedRulesAction: ignore   # documented as "same as if not specified, does nothing"
    rules:
    - name: DSC-Repro
      direction: Inbound
      action: Allow
      protocol: 6
      localPorts: '32921'
      enabled: true
dsc config test -f repro.dsc.yaml

Expected

inDesiredState: true. unspecifiedRulesAction: ignore is documented as "same as if not specified, does nothing", so it should be indistinguishable from omitting the property.

Actual

{ "inDesiredState": false, "differingProperties": ["unspecifiedRulesAction"] }

Removing the unspecifiedRulesAction line from the same config yields inDesiredState: true with differingProperties: []. The only variable is the property itself — actual firewall state is identical in both cases.

All three enum values reproduce it:

config inDesiredState differingProperties
property omitted true []
unspecifiedRulesAction: ignore false ["unspecifiedRulesAction"]
unspecifiedRulesAction: disable false ["unspecifiedRulesAction"]
unspecifiedRulesAction: remove false ["unspecifiedRulesAction"]

Cause

get_rules() returns unspecified_rules_action: None unconditionally, and the field is skip_serializing_if = "Option::is_none", so dsc resource get never emits the property:

{"actualState":{"rules":[{"name":"DSC-Repro","protocol":6,"localPorts":"32921", ... }]}}

The engine's synthetic test then compares a desired state that has the property against an actual state that lacks it, and records a difference. This matches the intent stated in #1579 that the property be write-only and absent from output state — but combined with synthetic test, "absent from output" produces permanent false drift.

Also affects set convergence reporting

After a set has fully converged, a second identical set still reports:

{ "changedProperties": ["unspecifiedRulesAction"] }

The underlying rule work is correctly idempotent (already-disabled rules are skipped), but every run reports a change. For fleet reporting this means a converged estate never shows as converged.

Suggested fix

Two options, in preference order:

  1. Exclude the property from state comparison. In Implement canonical _purge on Microsoft.Windows/FirewallRuleList for authoritative rule management #1579, Steve Lee (@SteveL-MSFT) argued for a canonical _-prefixed property (_disableUnspecified) partly for schema discoverability. A canonical write-only property would also sidestep this, since the engine already knows not to diff canonical metadata. If a resource-specific name is preferred, the resource or engine needs an equivalent "write-only, do not compare" marker.
  2. Echo the property back from get, defaulting to ignore when absent, so desired and actual agree.

Option 1 seems more correct — the property is an instruction, not observable state — but it needs a mechanism that doesn't exist for non-canonical properties today.

Impact

Any configuration using unspecifiedRulesAction permanently reports out-of-desired-state and reports a change on every run, regardless of actual firewall state. This makes the property unusable for compliance reporting, which is its primary use case.

Refs: #1579, #1599

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions