Skip to content

[AddsOrgUnitsAndGroups] Fix quadratic DependsOn producing a MOF larger than 50 MB#249

Merged
raandree merged 5 commits into
dsccommunity:mainfrom
raandree:fix/#248
Jul 9, 2026
Merged

[AddsOrgUnitsAndGroups] Fix quadratic DependsOn producing a MOF larger than 50 MB#249
raandree merged 5 commits into
dsccommunity:mainfrom
raandree:fix/#248

Conversation

@raandree

@raandree raandree commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Pull Request (PR) description

Fixes #248 .

AddsOrgUnitsAndGroups assigned the entire OU dependency list ($ouDependencies)
as the DependsOn of every DomainLocal group:

$group.DependsOn = $ouDependencies   # all OUs, on every DomainLocal group

Root cause. That produces O(groups x OUs) dependency edges. When the composite
is expanded into a root configuration, DSC fully-qualifies every ResourceID /
DependsOn reference (e.g.
[ADOrganizationalUnit]OUGroupsOUAdminDCcontosoDCcom::[AddsOrgUnitsAndGroups]AddsOrgUnitsAndGroups),
multiplying each edge's serialized size. On a real DC (~204 OUs x ~167 DomainLocal
groups, ~34,000 edges) the node MOF reached 52.5 MB and failed to compile with the hard
50 MB (52428800-byte) ValidateInstanceText limit, which is not tunable via
MaxEnvelopeSizeKb.

Fix. A group only needs its own parent OU to exist first — OUs already form a correct
creation chain via their own DependsOn. The parent OU's resource ID is reconstructed
from the group's Path using the same -replace '\W' normalisation already applied to
OU IDs, so a managed parent matches exactly; it falls back to the full list only when the
parent OU is not managed by the composite:

$group.Path = '{0},{1}' -f $group.Path, $DomainDn

$parentOuRef = "[ADOrganizationalUnit]$($group.Path -replace '\W')"
if ($ouDependencies -contains $parentOuRef)
{
    $group.DependsOn = $parentOuRef
}
else
{
    $group.DependsOn = $ouDependencies
}

Measured (real DC). Composite MOF 8.91 MB -> 0.49 MB; node MOF 52.5 MB -> 17.8 MB;
build green.

A regression test compiles the composite with two DomainLocal groups in different OUs
and asserts each gets exactly one [ADOrganizationalUnit] dependency (its own parent),
so the quadratic blow-up cannot silently return.

Fixed

  • AddsOrgUnitsAndGroups: each DomainLocal group now depends only on its own parent OU
    instead of the entire OU list. Assigning every OU as DependsOn to every DomainLocal
    group produced an O(groups x OUs) dependency explosion that, once the composite is
    expanded into a root configuration, could push a large domain controller's MOF past
    DSC's hard 50 MB ValidateInstanceText limit (serializedBufferLength must be in the range of 4 and 52428800). The parent OU is sufficient for correct apply ordering;
    falls back to the full list when the parent OU is not managed by the composite.

Task list

  • The PR represents a single logical change.
  • Added an entry under the Unreleased section of the CHANGELOG.md.
  • Local clean build passes without issue or fail tests (build.ps1 -ResolveDependency).
  • Resource documentation added/updated in README.md.
  • Resource parameter descriptions added/updated in README.md, schema.mof and comment-based help.
  • Comment-based help added/updated.
  • Localization strings added/updated in all localization files as appropriate.
  • Examples appropriately added/updated.
  • Unit tests added/updated.
  • Integration tests added/updated (where possible).
  • New/changed code adheres to DSC Resource Style Guidelines and Best Practices.

This change is Reviewable

@raandree raandree self-assigned this Jul 8, 2026
@raandree raandree added the bug Something isn't working label Jul 8, 2026
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ba4f7dd8-f04b-444b-90a4-2f0fc29234ba

📥 Commits

Reviewing files that changed from the base of the PR and between 00c6189 and eb969e0.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • source/DSCResources/AddsOrgUnitsAndGroups/AddsOrgUnitsAndGroups.schema.psm1
  • source/DSCResources/DscPullServerSql/DscPullServerSql.schema.psm1
  • tests/Unit/DSCResources/DscResources.Tests.ps1
💤 Files with no reviewable changes (1)
  • source/DSCResources/AddsOrgUnitsAndGroups/AddsOrgUnitsAndGroups.schema.psm1
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • source/DSCResources/DscPullServerSql/DscPullServerSql.schema.psm1
  • tests/Unit/DSCResources/DscResources.Tests.ps1

Walkthrough

This PR changes DomainLocal group dependency assignment to use a single managed parent OU when possible, adds a regression fixture and test for that behavior, removes defaults from DscPullServerSql mandatory parameters, and updates the changelog.

Changes

DomainLocal DependsOn fix

Layer / File(s) Summary
Parent OU DependsOn implementation
source/DSCResources/AddsOrgUnitsAndGroups/AddsOrgUnitsAndGroups.schema.psm1
Sets Path before dependency selection, derives a parent OU reference, and uses it for DependsOn when the parent OU is managed.
Regression fixture and test
tests/Unit/DSCResources/Assets/Config/AddsOrgUnitsAndGroups.yml, tests/Unit/DSCResources/DscResources.Tests.ps1
Adds a DomainLocal group fixture and a regression test that checks each compiled DomainLocal group has exactly one organizational-unit dependency.
Changelog entry
CHANGELOG.md
Documents the parent-only DependsOn behavior and the fallback to the full OU list when needed.

DscPullServerSql parameter defaults

Layer / File(s) Summary
Parameter defaults removed
source/DSCResources/DscPullServerSql/DscPullServerSql.schema.psm1
Removes default values from SqlServer and DatabaseName, leaving DatabaseName mandatory.
Changelog entry
CHANGELOG.md
Adds the corresponding changelog note about removing the mandatory-parameter defaults.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also changes DscPullServerSql and DfsNamespaces changelog entries, which are unrelated to the linked issue. Move the unrelated DscPullServerSql and DfsNamespaces changes into separate PRs or explain them with linked issues.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing the quadratic DependsOn behavior in AddsOrgUnitsAndGroups.
Description check ✅ Passed The description is detailed and directly describes the same AddsOrgUnitsAndGroups fix and regression test.
Linked Issues check ✅ Passed The changes match #248 by limiting DomainLocal groups to their parent OU when managed and falling back to all OUs otherwise.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/Unit/DSCResources/DscResources.Tests.ps1 (1)

234-273: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider splitting scenarios into separate Context and It blocks.

The test mixes two distinct scenarios — verifying each DomainLocal group has exactly one OU dependency, and validating a specific group's dependency ID — in a single It block. As per coding guidelines, each scenario should be a separate Context block, and Context descriptions should start with 'When'.

♻️ Suggested restructure
 Describe 'AddsOrgUnitsAndGroups DependsOn' -Tags FunctionalQuality {
+    Context 'When verifying DomainLocal group dependencies' {
+        It 'Should give each DomainLocal group exactly one parent-OU dependency' {
-    It 'Should give each DomainLocal group a single parent-OU dependency (no quadratic blow-up)' {
 
-        # Regression guard for the O(groups x OUs) DependsOn explosion: assigning the
-        # full OU dependency list to every DomainLocal group inflated the compiled MOF
-        # so that a large DC exceeded DSC's hard 50 MB ValidateInstanceText limit. Each
-        # DomainLocal group must depend on exactly one [ADOrganizationalUnit] - its own
-        # parent OU - not on every OU created by the composite.
+            # Regression guard for the O(groups x OUs) DependsOn explosion: assigning the
+            # full OU dependency list to every DomainLocal group inflated the compiled MOF
+            # so that a large DC exceeded DSC's hard 50 MB ValidateInstanceText limit. Each
+            # DomainLocal group must depend on exactly one [ADOrganizationalUnit] - its own
+            # parent OU - not on every OU created by the composite.
         $mofPath = Join-Path -Path $OutputDirectory -ChildPath 'localhost_AddsOrgUnitsAndGroups.mof'
         Test-Path -Path $mofPath | Should -BeTrue -Because 'the AddsOrgUnitsAndGroups composite must have compiled'
 
@@ -264,8 +267,12 @@
             $ouRefCount | Should -Be 1 -Because "DomainLocal group '$groupName' must depend only on its own parent OU, not the whole OU list"
         }
+        }
+    }
 
-        # The single dependency must be the group's own parent OU, using the same
-        # -replace '\W' normalisation the composite applies to OU resource IDs.
+    Context 'When verifying specific group dependency ID' {
+        It 'Should match the expected parent OU for App_123_Read' {
+            # The single dependency must be the group's own parent OU, using the same
+            # -replace '\W' normalisation the composite applies to OU resource IDs.
         $readGroupBlock = $domainLocalBlocks |
             Where-Object { $_ -match 'GroupName\s*=\s*"App_123_Read"' }
         $readGroupBlock | Should -Match '\[ADOrganizationalUnit\]OUGroupsOUAdminDCcontosoDCcom'
+        }
+    }
 }
🤖 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 `@tests/Unit/DSCResources/DscResources.Tests.ps1` around lines 234 - 273, Split
the current AddsOrgUnitsAndGroups DependsOn test in DscResources.Tests.ps1 into
two separate scenarios, using distinct Context blocks whose descriptions start
with “When”. Keep the existing DomainLocal dependency-count check in one It
block, and move the specific App_123_Read dependency-ID assertion into its own
It block under a separate Context. Use the existing test identifiers like
AddsOrgUnitsAndGroups DependsOn, domainLocalBlocks, and readGroupBlock to locate
the split cleanly.

Source: Path instructions

🤖 Prompt for all review comments with 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.

Inline comments:
In `@tests/Unit/DSCResources/DscResources.Tests.ps1`:
- Line 236: The Pester test case description in the `It` block should follow the
naming guideline and start with “Should”. Update the `It` description in
`DscResources.Tests.ps1` for the `gives each DomainLocal group a single
parent-OU dependency (no quadratic blow-up)` test so it begins with “Should”,
keeping the rest of the intent unchanged.

---

Nitpick comments:
In `@tests/Unit/DSCResources/DscResources.Tests.ps1`:
- Around line 234-273: Split the current AddsOrgUnitsAndGroups DependsOn test in
DscResources.Tests.ps1 into two separate scenarios, using distinct Context
blocks whose descriptions start with “When”. Keep the existing DomainLocal
dependency-count check in one It block, and move the specific App_123_Read
dependency-ID assertion into its own It block under a separate Context. Use the
existing test identifiers like AddsOrgUnitsAndGroups DependsOn,
domainLocalBlocks, and readGroupBlock to locate the split cleanly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 60d04859-7f86-448a-b7d4-db21771c43ca

📥 Commits

Reviewing files that changed from the base of the PR and between 9eca6d4 and b19fb22.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • source/DSCResources/AddsOrgUnitsAndGroups/AddsOrgUnitsAndGroups.schema.psm1
  • tests/Unit/DSCResources/Assets/Config/AddsOrgUnitsAndGroups.yml
  • tests/Unit/DSCResources/DscResources.Tests.ps1

Comment thread tests/Unit/DSCResources/DscResources.Tests.ps1 Outdated
@raandree raandree requested a review from dan-hughes July 8, 2026 14:33

@dan-hughes dan-hughes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@dan-hughes reviewed 4 files and all commit messages, and made 3 comments.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on raandree).


tests/Unit/DSCResources/DscResources.Tests.ps1 line 236 at r2 (raw file):

Describe 'AddsOrgUnitsAndGroups DependsOn' -Tags FunctionalQuality {

    It 'Should give each DomainLocal group a single parent-OU dependency (no quadratic blow-up)' {

This whole test doesn't really separate setup code that should be in a BeforeDiscovery, BeforeAll/BeforeEach and the code that should be in It.

The foreach should be in it's own It block and use the Pester code generation features.


tests/Unit/DSCResources/DscResources.Tests.ps1 line 252 at r2 (raw file):

        # nested '};' that terminates a DependsOn list.
        $adGroupBlocks = ($mofContent -split 'instance of ') |
            Where-Object { $_ -match '^MSFT_ADGroup\b' }

If the ADGroup resource prefix is ever changed/updated to match new conventions, this will break.


source/DSCResources/AddsOrgUnitsAndGroups/AddsOrgUnitsAndGroups.schema.psm1 line 96 at r2 (raw file):

    }

    $dependencies = @()

Not changed in this PR but assigning $dependencies from the foreach will give a performance increase.

Suggestion:

$dependencies = foreach ($group in $Groups)
    {
        # remove case sensitivity from hashtables
        $group = @{} + $group

        if ($group.GroupScope -eq 'DomainLocal')
        {
            "[ADGroup]'$($group.GroupName)'"

@dan-hughes dan-hughes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:lgtm:

@dan-hughes reviewed 4 files and all commit messages, made 1 comment, and resolved 3 discussions.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on raandree).

@raandree

raandree commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, @dan-hughes

@raandree raandree merged commit 64dda72 into dsccommunity:main Jul 9, 2026
7 checks passed
@raandree raandree deleted the fix/#248 branch July 9, 2026 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AddsOrgUnitsAndGroups: quadratic DependsOn produces a node MOF larger than the 50 MB limit

2 participants