Skip to main content

Access Rules

Synopsis

Define the desired access rules (ACEs) on objects within the forest's Configuration and Schema naming contexts.

Description

Forest-level access rules allow you to delegate privileges on any object in the forest's Configuration or Schema naming contexts — the parts of Active Directory that are managed by this module.

This component mirrors the Domain Access Rules component in design, but operates at the forest scope rather than the domain scope.

Any object that has at least one access rule defined is assumed to be fully managed. Access rules not present in the configuration will be removed from that object. Inherited permissions are ignored.

info

It is not necessary to define the access rules specified as default access rules for that object type by the Schema — ADMF is aware of schema defaults and will not flag them as excess.

Rules can be applied to a specific object path or to all objects matching an Object Category.

The Present property supports a third value, Undefined, which causes ADMF to accept a rule if it exists without creating or deleting it — useful for rules you want to leave untouched.

Example Configuration

Grant Domain Admins full control over all Domain Controllers (using an object category):

[
{
"ObjectCategory": "DomainControllers",
"Identity": "%DomainName%\\Domain Admins",
"ActiveDirectoryRights": "GenericAll",
"AccessControlType": "Allow",
"InheritanceType": "None",
"ObjectType": "<All>",
"InheritedObjectType": "<All>"
}
]

The same rule in psd1 format:

@(
@{
ObjectCategory = 'DomainControllers'
Identity = '%DomainName%\Domain Admins'
ActiveDirectoryRights = 'GenericAll'
AccessControlType = 'Allow'
InheritanceType = 'None'
ObjectType = '<All>'
InheritedObjectType = '<All>'
}
)

A path-based rule granting read access to an object in the Configuration partition:

[
{
"Path": "CN=Sites,%ConfigurationDN%",
"Identity": "Everyone",
"ActiveDirectoryRights": "ReadProperty",
"AccessControlType": "Allow",
"InheritanceType": "All",
"ObjectType": "<All>",
"InheritedObjectType": "<All>"
}
]

Tools

Copy access rules from an existing AD object:

$acl = Get-AdsAcl -Path 'CN=Sites,CN=Configuration,DC=contoso,DC=com'
$acl.Access |
Where-Object IdentityReference -eq 'CONTOSO\Domain Admins' |
Select-PSFObject @(
@{
Name = 'Path'
Expression = { $acl.DistinguishedName -replace 'CN=Configuration,DC=.+$', '%ConfigurationDN%' }
},
'ActiveDirectoryRights TO string',
'InheritanceType TO string',
'ObjectType',
'InheritedObjectType',
'AccessControlType TO string',
'IdentityReference AS Identity TO string'
) | ConvertTo-Json

Then update the Identity value as needed using Name Mapping / Name Resolution.

You can also use Convert-AdcSchemaGuid to translate GUIDs into human-readable names.

Transfer from Test Result

If you already have some settings deployed and are testing access rules using Test-AdmfForest, you can convert test results into configuration entries.

This command will convert test results into suitable datasets:

function ConvertTo-AccessRuleConfiguration {
<#
.SYNOPSIS
Tool to convert Access Rule test results into configuration sets.

.DESCRIPTION
Tool to convert Access Rule test results into configuration sets.

.PARAMETER Path
Replace the path the results should apply to.
By default, paths should be auto-detected.

.PARAMETER ObjectCategory
Name of the object category that the result should be applied to.
By default, rules are applied to paths of the origin.

.PARAMETER InputObject
The test result to convert.

.PARAMETER Clip
Converts results to json and pastes them to clipboard.

.PARAMETER NoResolve
Do not resolve GUIDs
GUIDs will be resovled using the last server used in DomainManagement, which may fail if credentials are needed.

.EXAMPLE
PS C:\> $res | ConvertTo-AccessRuleConfiguration

Converts the input test result to configuration rules

.EXAMPLE
PS C:\> $res | carc -ObjectCategory trustuser -Clip

Converts the input test result to configuration rules that apply to the object category "trustuser".
Then it converts the results to json and pastes it to the clipboard
#>
[Alias('carc')]
[CmdletBinding()]
param (
[string]
$Path,

[string]
$ObjectCategory,

[Parameter(ValueFromPipeline = $true)]
$InputObject,

[switch]
$Clip,

[switch]
$NoResolve
)

begin {
function Convert-Identity {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true)]
[string]
$Name
)

begin {
$builtIn = @{
'BUILTIN\Administrators' = 'S-1-5-32-544'
'BUILTIN\Users' = 'S-1-5-32-545'
'BUILTIN\Guests' = 'S-1-5-32-546'
'BUILTIN\Account Operators' = 'S-1-5-32-548'
'BUILTIN\Server Operators' = 'S-1-5-32-549'
'BUILTIN\Print Operators' = 'S-1-5-32-550'
'BUILTIN\Backup Operators' = 'S-1-5-32-551'
'BUILTIN\Replicator' = 'S-1-5-32-552'
'BUILTIN\Pre-Windows 2000 Compatible Access' = 'S-1-5-32-554'
'BUILTIN\Remote Desktop Users' = 'S-1-5-32-555'
'BUILTIN\Network Configuration Operators' = 'S-1-5-32-556'
'BUILTIN\Incoming Forest Trust Builders' = 'S-1-5-32-557'
'BUILTIN\Performance Monitor Users' = 'S-1-5-32-558'
'BUILTIN\Performance Log Users' = 'S-1-5-32-559'
'BUILTIN\Windows Authorization Access Group' = 'S-1-5-32-560'
'BUILTIN\Terminal Server License Servers' = 'S-1-5-32-561'
'BUILTIN\Distributed COM Users' = 'S-1-5-32-562'
'BUILTIN\IIS_IUSRS' = 'S-1-5-32-568'
'BUILTIN\Cryptographic Operators' = 'S-1-5-32-569'
'BUILTIN\Event Log Readers' = 'S-1-5-32-573'
'BUILTIN\Certificate Service DCOM Access' = 'S-1-5-32-574'
'BUILTIN\RDS Remote Access Servers' = 'S-1-5-32-575'
'BUILTIN\RDS Endpoint Servers' = 'S-1-5-32-576'
'BUILTIN\RDS Management Servers' = 'S-1-5-32-577'
'BUILTIN\Hyper-V Administrators' = 'S-1-5-32-578'
'BUILTIN\Access Control Assistance Operators' = 'S-1-5-32-579'
'BUILTIN\Remote Management Users' = 'S-1-5-32-580'
'BUILTIN\Storage Replica Administrators' = 'S-1-5-32-582'
}
}

process {
if ($builtIn[$Name]) { return $builtIn[$Name] }

$sid = $Name -as [System.Security.Principal.SecurityIdentifier]
if (-not $sid) {
try { $sid = ([System.Security.Principal.NTAccount]$Name).Translate([System.Security.Principal.SecurityIdentifier]) }
catch { return $Name }
}

# Case: Builtin SID
if (-not $sid.AccountDomainSid) { return $sid -as [string] }

$rid = ($sid.Value -split '-')[-1]
if (1000 -gt $rid) {
return "%DomainSID%-$rid"
}
$Name
}
}

$list = [System.Collections.ArrayList]@()

# Figure out last server used from the DC resolution message
$server = @((Get-PSFMessage | Where-Object String -eq 'Resolve-DomainController.Resolved' | Select-Object -Last 1).StringValue)[0]
if (-not $server) { $server = $env:USERDNSDOMAIN }
}
process {
$data = $InputObject
if ($InputObject.Changed) { $data = $InputObject.Changed }
foreach ($datum in $data) {
$source = $datum.ADObject
if ($datum.Configuration) { $source = $datum.Configuration }
$hash = [ordered]@{
Path = $Path
ObjectCategory = $ObjectCategory
Identity = $source.IdentityReference | Convert-Identity | Set-String -OldValue '^.+\\' -NewValue '%DomainNetBIOSName%\'
ActiveDirectoryRights = $source.ActiveDirectoryRights -as [string]
InheritanceType = $source.InheritanceType -as [string]
AccessControlType = $source.AccessControlType -as [string]
ObjectType = $source.ObjectType -as [string]
InheritedObjectType = $source.InheritedObjectType -as [string]
}
if (-not $NoResolve) {
$hash.ObjectType = Convert-AdcSchemaGuid -Server $server -Name $hash.ObjectType -OutType Name
$hash.InheritedObjectType = Convert-AdcSchemaGuid -Server $server -Name $hash.InheritedObjectType -OutType Name
}

if ($Path) { $hash.Remove('ObjectCategory') }
elseif ($ObjectCategory) { $hash.Remove('Path') }
else {
$hash.Remove('ObjectCategory')
if ($datum.DistinguishedName) {
$hash.Path = $datum.DistinguishedName | Set-String -OldValue 'DC=.+' -NewValue '%DomainDN%'
}
elseif ($InputObject.Identity) {
$hash.Path = $InputObject.Identity | Set-String -OldValue 'DC=.+' -NewValue '%DomainDN%'
}
else {
$hash.Path = "INSERT_HERE"
}
}

switch ($datum.Type) {
'Restore' { $hash.Present = 'false' }
}

if ($Clip) { $null = $list.Add([PSCustomObject]$hash)}
else { [PSCustomObject]$hash }
}
}
end {
if ($Clip) {
$list | ConvertTo-Json | Set-Clipboard
}
}
}

With that you can then convert results like this:

# Plain Convert
$res = Test-AdmfForest -Server contoso.com -Option AccessRule
$res | ConvertTo-AccessRuleConfiguration

# Short form straight to clipboard
$res | carc -ObjectCategory trustuser -Clip

Properties

Path

Optional: No | Default: None | ParameterSet: Path

This parameter uses name resolution.

The distinguished name of the AD object to apply the rule to.

ObjectCategory

Optional: No | Default: None | ParameterSet: Category

Instead of a specific path, specify an object category. The rule will apply to all objects in that category. Categories are defined using Register-AdcObjectCategory.

Identity

Optional: No | Default: None

The security principal to grant or deny rights to.

ActiveDirectoryRights

Optional: No | Default: None

The Active Directory rights to assign. Common values include GenericAll, ReadProperty, WriteProperty, CreateChild, DeleteChild, GenericRead. This is stored as a string to accommodate non-standard values that AD may still accept.

AccessControlType

Optional: Yes | Default: Allow

Whether this is an Allow or Deny rule.

InheritanceType

Optional: Yes | Default: None

How the ACE propagates to child objects:

ValueMeaning
NoneApplies only to the object itself
AllApplies to the object and all descendants
DescendentsApplies to children and their descendants, but not the object itself
SelfAndChildrenApplies to the object and its immediate children
ChildrenApplies to immediate children only

ObjectType

Optional: Yes | Default: <All>

The GUID or name of the property, object class, or extended right the rule governs. <All> (equivalent to 00000000-0000-0000-0000-000000000000) means the rule applies to all types.

InheritedObjectType

Optional: Yes | Default: <All>

The GUID or name of the child object class to which the rule applies when using inheritance. <All> means the rule applies regardless of child object type.

Optional

Optional: Yes | Default: $false

When $true, the path this rule targets is not required to exist. The rule is applied only if the object is present; missing objects are silently ignored. Note: an object is considered optional only when all of its configured access rules have Optional = $true.

Present

Optional: Yes | Default: true

Whether the access rule should exist:

  • true — create the rule if missing (default).
  • false — explicitly delete the rule if it exists.
  • Undefined — accept the rule if it exists, but do not create or remove it.

NoFixConfig

Optional: Yes | Default: $false

By default, Test-FMAccessRule generates a FixConfig result when a rule that is explicitly configured is also part of the Schema default permissions. Set this to $true to suppress that result.