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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions