Skip to content

fix: language-independent AD principal resolution; auto-provision KDS… - #18

Closed
FlorianGross wants to merge 2 commits into
microsoft:mainfrom
FlorianGross:fix/localized-ad-support
Closed

fix: language-independent AD principal resolution; auto-provision KDS…#18
FlorianGross wants to merge 2 commits into
microsoft:mainfrom
FlorianGross:fix/localized-ad-support

Conversation

@FlorianGross

Copy link
Copy Markdown

Deployments, prerequisite checks and WinLAPS delegations failed on non-English Active Directory domains (e.g. German: "Domain Admins" is "Domänen-Admins", "Enterprise Admins" is "Organisations-Admins") because well-known principals were looked up by their English display names. All well-known principals are now resolved language-independently via their fixed SIDs/RIDs. Validated against a real German AD (full deployment run).

SID/RID resolution (Resolve-TierModelPrincipalSid):

  • New WellKnownRid resolution stage that maps canonical names of well-known domain groups to - before any name-based AD lookup (Guest=501, krbtgt=502, Domain Admins=512, Domain Users=513, Domain Controllers=516, Cert Publishers=517, GP Creator Owners=520, RODCs=521, Cloneable DCs=522, Protected Users=525, Key Admins=526, RAS and IAS Servers=553, Allowed/Denied RODC Password Replication Group=571/572).
  • Forest-level groups (Enterprise RODCs=498, Schema Admins=518, Enterprise Admins=519, Enterprise Key Admins=527) resolve against the forest root domain SID, correct also from child domains.
  • Well-known SID table extended with all BUILTIN groups (Cryptographic Operators, Certificate Service DCOM Access, RDS groups, Hyper-V Administrators, Remote Management Users, Device Owners, User Mode Hardware Operators, OpenSSH Users, ...) including short-name aliases as used in tiermodel-gpos.json (e.g. "Backup Operators").
  • Domain SIDs are cached per domain controller; on failure the existing name-based resolution is used as fallback.
  • Resolve-ADPrincipalSid: DomainController is now an explicit parameter instead of relying on dynamic scoping.

Prerequisites (Test-TierModelPrerequisites):

  • Domain Admins membership check and Enterprise Admins detection resolve via RID 512/519 first, with name-based fallback.

GPO deployment:

  • New-TierModelGpo: the Deny "Apply Group Policy" ACE is built from a SecurityIdentifier resolved via Resolve-TierModelPrincipalSid instead of an NTAccount name, which failed to translate on localized domains ("Some or all identity references could not be translated").
  • tiermodel-gpos.json: literal "Power Users" / "User Mode Hardware Operators" restricted-group entries replaced with their well-known SIDs (*S-1-5-32-547 / *S-1-5-32-584) so target machines do not have to resolve English names client-side.

Windows LAPS:

  • Get-TierModelWinLapsAcl/Fd resolved readGroup/resetGroup/ decryptorGroup via Get-ADGroup -Filter "Name -eq '...'" — fails for well-known groups on non-English ADs, leaving allowedPrincipals empty and crashing Set-LapsADReadPasswordPermission with "Cannot bind argument ... empty array". Well-known groups are now resolved via their fixed SID/RID first, with name lookup as fallback.
  • New-TierModelWinLapsAcl fails the action with a clear error when a Read/Reset permission has no resolvable principals instead of surfacing a parameter binding exception.

Auto-provisioning (execution mode only, -ConfirmApply):

  • -IncludeGmsa/-IncludeDmsa: if no KDS Root Key exists, Deploy-TierModel creates one on the preferred DC via Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10)) so it is effective immediately.
  • -IncludeWinLaps: if the msLAPS-* schema attributes are missing, the AD schema is extended automatically via Update-LapsADSchema (requires Schema Admins).
  • Prerequisites remediation text updated accordingly.

Tests:

  • 17 new unit tests for RID/BUILTIN resolution (child domain, caching, fallback, case-insensitivity) and a prerequisites test simulating a German AD (group only findable as "Domänen-Admins" via RID 512).

The English group names in the config files remain unchanged and act as canonical identifiers; no config changes are required for existing deployments.

… Root Key and Windows LAPS schema

Deployments, prerequisite checks and WinLAPS delegations failed on
non-English Active Directory domains (e.g. German: "Domain Admins" is
"Domänen-Admins", "Enterprise Admins" is "Organisations-Admins")
because well-known principals were looked up by their English display
names. All well-known principals are now resolved language-independently
via their fixed SIDs/RIDs. Validated against a real German AD
(full deployment run).

SID/RID resolution (Resolve-TierModelPrincipalSid):
- New WellKnownRid resolution stage that maps canonical names of
  well-known domain groups to <DomainSID>-<RID> before any name-based
  AD lookup (Guest=501, krbtgt=502, Domain Admins=512, Domain
  Users=513, Domain Controllers=516, Cert Publishers=517, GP Creator
  Owners=520, RODCs=521, Cloneable DCs=522, Protected Users=525, Key
  Admins=526, RAS and IAS Servers=553, Allowed/Denied RODC Password
  Replication Group=571/572).
- Forest-level groups (Enterprise RODCs=498, Schema Admins=518,
  Enterprise Admins=519, Enterprise Key Admins=527) resolve against the
  forest root domain SID, correct also from child domains.
- Well-known SID table extended with all BUILTIN groups (Cryptographic
  Operators, Certificate Service DCOM Access, RDS groups, Hyper-V
  Administrators, Remote Management Users, Device Owners, User Mode
  Hardware Operators, OpenSSH Users, ...) including short-name aliases
  as used in tiermodel-gpos.json (e.g. "Backup Operators").
- Domain SIDs are cached per domain controller; on failure the existing
  name-based resolution is used as fallback.
- Resolve-ADPrincipalSid: DomainController is now an explicit parameter
  instead of relying on dynamic scoping.

Prerequisites (Test-TierModelPrerequisites):
- Domain Admins membership check and Enterprise Admins detection
  resolve via RID 512/519 first, with name-based fallback.

GPO deployment:
- New-TierModelGpo: the Deny "Apply Group Policy" ACE is built from a
  SecurityIdentifier resolved via Resolve-TierModelPrincipalSid instead
  of an NTAccount name, which failed to translate on localized domains
  ("Some or all identity references could not be translated").
- tiermodel-gpos.json: literal "Power Users" / "User Mode Hardware
  Operators" restricted-group entries replaced with their well-known
  SIDs (*S-1-5-32-547 / *S-1-5-32-584) so target machines do not have
  to resolve English names client-side.

Windows LAPS:
- Get-TierModelWinLapsAcl/Fd resolved readGroup/resetGroup/
  decryptorGroup via Get-ADGroup -Filter "Name -eq '...'" — fails for
  well-known groups on non-English ADs, leaving allowedPrincipals empty
  and crashing Set-LapsADReadPasswordPermission with "Cannot bind
  argument ... empty array". Well-known groups are now resolved via
  their fixed SID/RID first, with name lookup as fallback.
- New-TierModelWinLapsAcl fails the action with a clear error when a
  Read/Reset permission has no resolvable principals instead of
  surfacing a parameter binding exception.

Auto-provisioning (execution mode only, -ConfirmApply):
- -IncludeGmsa/-IncludeDmsa: if no KDS Root Key exists, Deploy-TierModel
  creates one on the preferred DC via Add-KdsRootKey -EffectiveTime
  ((Get-Date).AddHours(-10)) so it is effective immediately.
- -IncludeWinLaps: if the msLAPS-* schema attributes are missing, the
  AD schema is extended automatically via Update-LapsADSchema (requires
  Schema Admins).
- Prerequisites remediation text updated accordingly.

Tests:
- 17 new unit tests for RID/BUILTIN resolution (child domain, caching,
  fallback, case-insensitivity) and a prerequisites test simulating a
  German AD (group only findable as "Domänen-Admins" via RID 512).

The English group names in the config files remain unchanged and act as
canonical identifiers; no config changes are required for existing
deployments.
Copilot AI review requested due to automatic review settings July 24, 2026 14:49
@FlorianGross

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copilot AI 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.

Pull request overview

This PR improves Active Directory tier-model deployments on non-English domains by resolving well-known principals language-independently (SID/RID-based), and adds execution-mode auto-provisioning for optional prerequisites (KDS root key, Windows LAPS schema).

Changes:

  • Add RID-based resolution for well-known domain/forest principals (plus expanded BUILTIN well-known SID table) and propagate explicit -DomainController usage through SID resolution.
  • Update prerequisite checks, WinLAPS planning/execution, and GPO ACL manipulation to avoid localized-name lookups that fail on non-English ADs.
  • Extend configuration/test coverage (new unit tests; replace localized restricted-group entries with well-known SIDs in tiermodel-gpos.json).

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/Unit.Resolution.Tests.ps1 Adds unit coverage for RID/BUILTIN resolution, caching, and fallback behaviors.
tests/Unit.Prerequisites.Tests.ps1 Adds a localized-domain (German) prerequisite scenario test for Domain Admin resolution via RID 512.
modules/TierModel/public/Test-TierModelPrerequisites.ps1 Updates DA/EA detection to try RID-based resolution before name lookup; adjusts KDS remediation text.
modules/TierModel/public/Resolve-TierModelPrincipalSid.ps1 Adds well-known RID stage, forest-root SID caching, expands well-known SID table, and makes AD resolution take explicit DC parameter.
modules/TierModel/public/New-TierModelWinLapsAcl.ps1 Adds guardrails to fail clearly when read/reset principals cannot be resolved.
modules/TierModel/public/New-TierModelGpo.ps1 Builds deny “Apply GPO” ACE from resolved SID rather than localized NTAccount translation.
modules/TierModel/public/Get-TierModelWinLapsAclFd.ps1 Updates WinLAPS FD planning group resolution to use SID/RID-first resolution for well-known groups.
modules/TierModel/public/Get-TierModelWinLapsAcl.ps1 Updates WinLAPS planning group resolution to use SID/RID-first resolution for well-known groups.
Deploy-TierModel.ps1 Adds optional prerequisite auto-provisioning (KDS root key, LAPS schema) in execution mode with -ConfirmApply.
config/tiermodel-gpos.json Replaces localized restricted-group names with well-known BUILTIN SIDs for client-side language independence.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread modules/TierModel/public/Resolve-TierModelPrincipalSid.ps1
Comment thread modules/TierModel/public/Test-TierModelPrerequisites.ps1 Outdated
Comment thread modules/TierModel/public/Get-TierModelWinLapsAcl.ps1
Comment thread modules/TierModel/public/Get-TierModelWinLapsAclFd.ps1
…rom child domains

- Get-TierModelDomainSid: query the forest root domain with
  -Server $forest.RootDomain so a DC of the root domain (within the
  targeted forest) answers, instead of relying on client-side discovery.
- Test-TierModelPrerequisites: the Enterprise Admins existence check now
  targets a root-domain DC when running from a child domain; previously
  the SID lookup against the child DC could report HasEnterpriseAdmins
  as false even though the group exists in the forest root.
- Get-TierModelWinLapsAcl/Fd: well-known SIDs are translated locally
  (SecurityIdentifier -> NTAccount) before falling back to an AD lookup.
  Translation returns the correct domain prefix even for forest-root
  groups (e.g. Enterprise Admins) in child domains, avoiding invalid
  CHILD\<group> principals built from the current domain's NetBIOS name.
Copilot AI review requested due to automatic review settings July 24, 2026 15:43

Copilot AI 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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

modules/TierModel/public/Get-TierModelWinLapsAcl.ps1:244

  • When resolving well-known groups via SID and the local SID→NTAccount translation fails, the fallback path uses "$netBIOSDomain\$sAMAccountName". This produces incorrect principals for BUILTIN groups (should be "BUILTIN\…") and can also be wrong for forest-root groups when running from a child domain (prefix should be the owning domain’s NetBIOS name, not the current domain). This can cause the subsequent Set-LapsAD*Permission calls to target a non-existent group.
                if ($adGroup) {
                    $groupResolution[$group] = "$netBIOSDomain\$($adGroup.sAMAccountName)"
                } else {

modules/TierModel/public/Get-TierModelWinLapsAclFd.ps1:194

  • Same issue as Get-TierModelWinLapsAcl.ps1: if SID→NTAccount translation fails and the code falls back to Get-ADGroup by SID, the principal is reconstructed using the current domain’s NetBIOS name. This is incorrect for BUILTIN groups (should be "BUILTIN\…") and can be incorrect for forest-root groups when invoked from a child domain.
                if ($adGroup) {
                    $groupResolution[$group] = "$netBIOSDomain\$($adGroup.sAMAccountName)"
                }

@dakhama-mehdi

Copy link
Copy Markdown

Hello, my German friend! I made the same observation. In my opinion, he should at least document this limitation or add a check: if the operating system’s original installation language is not English, the script should not continue.

However, many account and group names are hard-coded throughout the project. A single push will not solve the problem; he needs to review the entire identification strategy, particularly by using SIDs to make the project language-independent.

@VAsHachiRoku

VAsHachiRoku commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Please review the language page for more details - https://microsoft.github.io/ActiveDirectoryTierModel/language-support/

I like the idea and it's something we should revisit at a later date; the main feature release is auth silos. In the meantime I will run through the code and come up with some ideas on how we could introduce additional language support, but this would require a community effort to preform testing.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants