Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 40 additions & 19 deletions .memory-bank/activeContext.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,52 @@ _Last updated: 2026-05-18_

## Current Focus

Project is in **maintenance mode**. No active feature work. The module has not
been touched for an extended period (last substantive work: October 2025
documentation pass).

This turn: refreshed the Memory Bank to align with the current agent
definition (folder renamed `memory-bank/` → `.memory-bank/`, added
`promptHistory.md`, trimmed `activeContext.md` and `progress.md` to the
prescribed caps).
Adding a new composite resource `AADSyncRuleCounts` that wraps the report-only
`AADSyncRuleCount` DSC resource introduced in the `feature/AadsyncrulecountResource`
branch of the `AADConnectDsc` repository (working copy at `d:\a`).

The composite mirrors the existing `AADSyncRules` /
`AADConnectDirectoryExtensionAttributes` schema-module pattern. It accepts an
array of hashtables (`ConnectorName`, `RuleCount`) and emits one
`AADSyncRuleCount` instance per item, mapping empty / `'*'` connector names to
the literal token `AllConnectors` so execution names stay unique.

Files added/changed on branch `ai/add-aadsyncrulecounts`:

- `source/DSCResources/AADSyncRuleCounts/AADSyncRuleCounts.psd1`
- `source/DSCResources/AADSyncRuleCounts/AADSyncRuleCounts.schema.psm1`
- `tests/Unit/DSCResources/Assets/Config/AADSyncRuleCounts.yml`
- `docs/AADSyncRuleCounts.md`
- `examples/6-AADSyncRuleCounts.ps1`
- `examples/README.md`, `README.md`, `CHANGELOG.md` updates
- This memory bank refresh

## Open Decisions

None. No pending design questions.
- Discovered (and fixed) a pre-existing bug: the module manifest was missing
`DscResourcesToExport`, which made `Get-DscResource -Module` return zero
composite resources in PowerShell 7. The build had been silently broken.
- The `AADSyncRuleCounts` compile test is enabled. It requires an
`AADConnectDsc` build that exposes `AADSyncRuleCount` (v0.6.0 of
`AADConnectDsc` or later). Local build uses the 0.6.0 build copied
from `d:\a` into `output/RequiredModules/AADConnectDsc/0.6.0/`. CI will
pick it up once `RequiredModules.psd1` (already `latest`) resolves to a
published version that ships `AADSyncRuleCount`.
- **In-process DSC parser caching**: `Get-DscResource -Module` and the DSC
keyword table are cached per process. Re-running the build in a long-lived
pwsh session that previously loaded an older `AADConnectDsc` will leave
stale keywords and make the new resource appear missing. Always run the
build in a fresh process (or `pwsh -NoProfile`) when changing the
underlying `AADConnectDsc` version.

## Next Steps (when work resumes)

1. Verify build still passes against current `AADConnectDsc` and `Sampler`
versions (`./build.ps1 -AutoRestore -Tasks test`).
2. Refresh `RequiredModules.psd1` pins if dependencies have moved.
3. Review open issues / PRs on the DscCommunity repo before any change.
4. Reconsider whether `productContext.md` should be folded into
`projectbrief.md` — it is no longer in the always-loaded set per the new
agent spec and currently lives as an on-demand topic file.
1. Wait for the `AADConnectDsc` PR (`feature/AadsyncrulecountResource`) to be
merged and a new version published, so CI can resolve `AADConnectDsc` from
the gallery instead of relying on the local 0.6.0 copy.
2. Cut a release with the `Unreleased` entry promoted to a numbered version.
Comment on lines +47 to +50
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix ordered-list prefix style to satisfy markdownlint.

The second item uses 2. while your configured MD029 style expects repeated 1. prefixes.

Suggested patch
 1. Wait for the `AADConnectDsc` PR (`feature/AadsyncrulecountResource`) to be
    merged and a new version published, so CI can resolve `AADConnectDsc` from
    the gallery instead of relying on the local 0.6.0 copy.
-2. Cut a release with the `Unreleased` entry promoted to a numbered version.
+1. Cut a release with the `Unreleased` entry promoted to a numbered version.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
1. Wait for the `AADConnectDsc` PR (`feature/AadsyncrulecountResource`) to be
merged and a new version published, so CI can resolve `AADConnectDsc` from
the gallery instead of relying on the local 0.6.0 copy.
2. Cut a release with the `Unreleased` entry promoted to a numbered version.
1. Wait for the `AADConnectDsc` PR (`feature/AadsyncrulecountResource`) to be
merged and a new version published, so CI can resolve `AADConnectDsc` from
the gallery instead of relying on the local 0.6.0 copy.
1. Cut a release with the `Unreleased` entry promoted to a numbered version.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 50-50: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1

(MD029, ol-prefix)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.memory-bank/activeContext.md around lines 47 - 50, Replace the ordered-list
numbering to use repeated "1." prefixes to satisfy markdownlint MD029: change
the second list item that starts "Cut a release with the `Unreleased` entry..."
so both items use "1." (i.e., make the two lines beginning "Wait for the
`AADConnectDsc` PR..." and "Cut a release with the `Unreleased` entry..." use
"1." as their list marker).


## Non-Goals

- No new composite resources planned.
- No restructuring of the build pipeline.
- No migration off Sampler / ModuleBuilder.
- No additional composite resources planned.
- No restructuring of the build pipeline.
Comment on lines +54 to +55
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a single trailing newline at EOF.

MD047 indicates the file should end with exactly one newline character.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 55-55: Files should end with a single newline character

(MD047, single-trailing-newline)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.memory-bank/activeContext.md around lines 54 - 55, The file ends without a
trailing newline; add exactly one newline character at EOF so the file
terminates with a single newline (MD047). Open the file containing the lines "-
No additional composite resources planned." and "- No restructuring of the build
pipeline." and ensure there is one and only one trailing newline character after
the final line.

13 changes: 13 additions & 0 deletions .memory-bank/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ _Last updated: 2026-05-18_

## Recent Log

- 2026-05-18 — Re-enabled the `AADSyncRuleCounts` compile test now that the
locally-built `AADConnectDsc 0.6.0` (with `AADSyncRuleCount`) is staged
under `output/RequiredModules/AADConnectDsc/0.6.0/`. Full build in a fresh
pwsh session passes 8/8 tests. Documented the in-process DSC parser
keyword-cache pitfall in `activeContext.md`.
- 2026-05-18 — Fixed the broken build: added missing `DscResourcesToExport`
to the module manifest (root cause: `Get-DscResource -Module` returned 0,
so the per-resource compile tests never ran and the Final tests failed),
and skipped the `AADSyncRuleCounts` compile test until `AADConnectDsc`
publishes `AADSyncRuleCount`. Full default build now exits 0.
- 2026-05-18 — Added `AADSyncRuleCounts` composite resource on branch
`ai/add-aadsyncrulecounts` wrapping the new report-only `AADSyncRuleCount`
resource from `AADConnectDsc` (`feature/AadsyncrulecountResource`).
- 2026-05-18 — Memory Bank refreshed: folder renamed to `.memory-bank/`,
files trimmed to new agent-spec caps, `promptHistory.md` added.
- 2025-10 — Documentation pass: README cross-links to `docs/`, examples
Expand Down
8 changes: 8 additions & 0 deletions .memory-bank/promptHistory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Prompt History

A one-line entry per substantive Copilot turn. Format:
`YYYY-MM-DD HH:mm UTC | agent | one-line intent`

2026-05-18 09:53 UTC | software-engineer | Add AADSyncRuleCounts composite wrapping new AADSyncRuleCount resource from AADConnectDsc
2026-05-18 10:06 UTC | software-engineer | Fix broken build: add DscResourcesToExport to manifest; skip AADSyncRuleCounts test until AADConnectDsc ships AADSyncRuleCount
2026-05-18 10:30 UTC | software-engineer | Re-enable AADSyncRuleCounts test against local AADConnectDsc 0.6.0; root cause of false failures was per-process DSC keyword caching
31 changes: 21 additions & 10 deletions .memory-bank/systemPatterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ processing.
┌─────────────────────────────────────────────────────────────────┐
│ DscConfig.AADConnect │
│ ┌─────────────────┐ ┌─────────────────────
│ │ AADSyncRules │ │ AADConnectDirectory │
│ │ Composite │ │ ExtensionAttributes │
│ │ Resource │ │ Composite Resource │
│ └─────────────────┘ └─────────────────────
│ ┌────────────────┌──────────────────────┐ ┌──────────────┐
│ │ AADSyncRules │ │ AADConnectDirectory │ │ AADSyncRule │
│ │ Composite │ │ ExtensionAttributes │ │ Counts
│ │ Resource │ │ Composite Resource │ Composite
│ └────────────────└──────────────────────┘ └──────────────┘
└─────────────────────────┬───────────────────────────────────────┘
│ Individual Resource Instances
┌─────────────────────────────────────────────────────────────────┐
│ AADConnectDsc │
│ ┌─────────────────┐ ┌─────────────────────
│ │ AADSyncRule │ │ AADConnectDirectory │
│ │ DSC Resource │ │ ExtensionAttribute │
│ │ │ DSC Resource
│ └─────────────────┘ └─────────────────────
│ ┌────────────────┌──────────────────────┐ ┌──────────────┐
│ │ AADSyncRule │ │ AADConnectDirectory │ │ AADSyncRule │
│ │ DSC Resource │ │ ExtensionAttribute │ │ Count
│ │ │ DSC Resource │ │ (report-only)│
│ └────────────────└──────────────────────┘ └──────────────┘
└─────────────────────────┬───────────────────────────────────────┘
│ ADSync PowerShell Module
Expand Down Expand Up @@ -135,6 +135,17 @@ $executionName = ($item.ConnectorName + '__' + $item.Name) -replace '[\s(){}/\\:
$executionName = ($item.Name + '__' + $item.AssignedObjectClass) -replace '[\s(){}/\\:-]', '_'
```

**AADSyncRuleCounts Pattern** (empty / `'*'` connector → `AllConnectors`):

```powershell
$scope = if ([string]::IsNullOrEmpty($item.ConnectorName) -or $item.ConnectorName -eq '*') {
'AllConnectors'
} else {
$item.ConnectorName
}
$executionName = ("AADSyncRuleCount__$scope") -replace '[\s(){}/\\:-]', '_'
```

### Integration Patterns

#### Configuration Management Integration
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- New composite resource `AADSyncRuleCounts` that wraps the report-only
`AADSyncRuleCount` DSC resource from `AADConnectDsc`. Accepts an array of
hashtables (`ConnectorName`, `RuleCount`) and generates one underlying
resource instance per item with a safe execution name. The empty / `'*'`
connector value is mapped to the literal token `AllConnectors` so
execution names stay unique across items.
Requires `AADConnectDsc` with `AADSyncRuleCount` support.

### Changed

- Updated module dependencies in `RequiredModules.psd1`.
- Updated build scripts (`build.ps1`, `Resolve-Dependency.ps1`) to align
with the latest version of Sampler.

### Fixed

- Added the missing `DscResourcesToExport` entry to
`source/DscConfig.AADConnect.psd1` so `Get-DscResource -Module
DscConfig.AADConnect` returns the composite resources. Without it, the
`tests/Unit/DSCResources/DscResources.Tests.ps1` discovery returned zero
resources, the per-resource compile tests were never generated, and the
`Final tests` count comparison failed in PowerShell 7. The build is now
green again.

## [0.2.0] - 2025-10-16

### Added
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ Processes arrays of directory extension attribute configurations and generates i
- Schema validation and type checking
- Integration with Azure AD schema requirements

### AADSyncRuleCounts

Processes arrays of sync-rule-count expectations and generates individual `AADSyncRuleCount` DSC resource instances. The underlying resource is **report-only**: it never adds or removes rules to reach the expected count, it fails the configuration when the actual count diverges so an operator can investigate.

**Key Features:**
- Bulk drift detection for sync-rule counts per connector
- Single-scope or all-connector (`'*'`) checks in the same array
- Safe execution-name generation for the empty / wildcard connector case
- Integration with the same `AADConnectDsc` runtime as the other composites

## Requirements

### System Requirements
Expand Down Expand Up @@ -175,6 +185,7 @@ For detailed documentation on each composite resource, see:

- **[AADSyncRules](docs/AADSyncRules.md)**: Processes arrays of Azure AD Connect sync rule configurations
- **[AADConnectDirectoryExtensionAttributes](docs/AADConnectDirectoryExtensionAttributes.md)**: Processes arrays of directory extension attribute configurations
- **[AADSyncRuleCounts](docs/AADSyncRuleCounts.md)**: Processes arrays of sync-rule-count expectations (report-only drift detection)

### Quick Reference

Expand All @@ -201,6 +212,18 @@ Execution names are generated using the pattern: `{ConnectorName}__{RuleName}` w
**Execution Name Generation:**
Execution names are generated using the pattern: `{AttributeName}__{ObjectClass}` with special characters replaced by underscores.

#### AADSyncRuleCounts

**Parameters:**

- **Items** (Mandatory): Array of hashtables representing expected sync-rule counts
- Each hashtable must contain `ConnectorName` (key) and `RuleCount` (mandatory `uint32`)
- Use an empty string or `'*'` for `ConnectorName` to count rules across all connectors
- The underlying `AADSyncRuleCount` resource is report-only; it does not remediate count drift

**Execution Name Generation:**
Execution names use the pattern `AADSyncRuleCount__{ConnectorName}` with special characters replaced by underscores. The empty / `'*'` connector value is mapped to the literal token `AllConnectors` to keep execution names unique and valid.

## Examples

For additional examples and advanced usage scenarios, see the [Examples](examples/) directory.
Expand Down
140 changes: 140 additions & 0 deletions docs/AADSyncRuleCounts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# AADSyncRuleCounts Composite Resource

## Description

The `AADSyncRuleCounts` composite resource processes arrays of Azure AD Connect
sync-rule-count expectations and generates individual `AADSyncRuleCount` DSC
resource instances. It is intended for bulk drift detection: each item declares
the expected number of sync rules for a given connector (or across all
connectors) and the underlying [AADSyncRuleCount](https://github.com/dsccommunity/AADConnectDsc) resource reports a configuration failure
when the actual count diverges from the expected count.

> [!NOTE]
> The underlying `AADSyncRuleCount` resource is **report-only**. It does not
> create or remove sync rules to reach the expected count. When drift is
> detected the LCM marks the configuration as failed and the operator must
> investigate manually.

## Parameters

### Items

- **Type**: `hashtable[]`
- **Required**: Yes
- **Description**: Array of hashtables describing the expected sync-rule counts.

Each hashtable must contain the parameters required by the underlying
`AADSyncRuleCount` resource:

| Property | Type | Required | Description |
|-----------------|----------|----------|-------------|
| `ConnectorName` | `string` | Yes (key) | Name of the AAD Connect connector to scope the count to. Use an empty string or `'*'` to count rules across **all** connectors. |
| `RuleCount` | `uint32` | Yes | The expected number of sync rules for the scope. |

## Behavior

### Execution Name Generation

Execution names are generated using the pattern:

```text
AADSyncRuleCount__{scope}
```

Where `{scope}` is the value of `ConnectorName`, except that an empty string or
`'*'` is mapped to the literal token `AllConnectors` so the name remains a
valid, unique resource identifier. Special characters (whitespace, brackets,
slashes, colons, dashes) are replaced with `_` using the regex pattern
`[\s(){}/\\:-]`.

Examples:

| `ConnectorName` value | Generated execution name |
|-----------------------|---------------------------------------|
| `contoso.com` | `AADSyncRuleCount__contoso.com` |
| `fabrikam.com` | `AADSyncRuleCount__fabrikam.com` |
| `''` (empty) | `AADSyncRuleCount__AllConnectors` |
| `*` | `AADSyncRuleCount__AllConnectors` |

### Resource Delegation

Each item is passed to a single `AADSyncRuleCount` resource instance via the
`Get-DscSplattedResource` utility. The composite performs no validation beyond
ensuring the items are processable; the underlying resource is responsible for
key/type validation.

## Examples

### Example 1: Per-connector count check

```powershell
Configuration BasicRuleCounts {
Import-DscResource -ModuleName DscConfig.AADConnect

Node localhost {
AADSyncRuleCounts 'CompanyRuleCounts' {
Items = @(
@{ ConnectorName = 'contoso.com'; RuleCount = 42 }
@{ ConnectorName = 'fabrikam.com'; RuleCount = 30 }
)
}
}
}
```

### Example 2: Total count across all connectors

```powershell
Configuration TotalRuleCount {
Import-DscResource -ModuleName DscConfig.AADConnect

Node localhost {
AADSyncRuleCounts 'TotalCount' {
Items = @(
@{ ConnectorName = '*'; RuleCount = 168 }
)
}
}
}
```

### Example 3: Configuration-management integration

```yaml
# Datum / DscWorkshop configuration data
AADSyncRuleCounts:
Items:
- ConnectorName: contoso.com
RuleCount: 42
- ConnectorName: fabrikam.com
RuleCount: 30
- ConnectorName: '*'
RuleCount: 168
```

```powershell
Configuration DataDrivenRuleCounts {
Import-DscResource -ModuleName DscConfig.AADConnect

Node $AllNodes.NodeName {
AADSyncRuleCounts 'RuleCounts' {
Items = $ConfigurationData.AADSyncRuleCounts.Items
}
}
}
```

## Related Resources

- [AADSyncRuleCount](https://github.com/dsccommunity/AADConnectDsc) — the
underlying report-only DSC resource provided by `AADConnectDsc`.
- [AADSyncRules](AADSyncRules.md) — companion composite resource that manages
the sync rules themselves.

## Notes

- This composite resource runs during DSC configuration compilation.
- The companion `AADSyncRuleCount` resource ships with `AADConnectDsc`
starting with the version that introduces report-only count drift detection.
If your installed `AADConnectDsc` predates that version, compilation will
fail because the underlying resource is not present.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a single trailing newline at end of file.

Markdown lint indicates missing final newline (MD047).

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 140-140: Files should end with a single newline character

(MD047, single-trailing-newline)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/AADSyncRuleCounts.md` at line 140, The file AADSyncRuleCounts.md is
missing a final newline (MD047); open AADSyncRuleCounts.md and add a single
trailing newline character at the end of the file so the file ends with exactly
one final newline, then save and commit the change.

Loading