Building Highly-Available Windows Infrastructure: Command-line Style. AD DS. Part 2 — Post-Config

Previous part — Building Highly-Available Windows Infrastructure: Command-line Style. AD DS. Part 1 — Installation


In this post we will perform two configurations on our Active Directory Domain Services instance: We’ll define security tiers which later become cornerstones of our privilege delegation principles and we’ll tune domain-joining parameters. Also a quick tweak for the DNS service.

Variables used in this article:


Defining Security Tiers

What are the security tiers?

Careful privilege delegation planning is very important when you work with information systems, especially when you work with Active Directory. Ideally, you should implement the least privilege model and split your network into separated security tiers. Several security tiers, though, can be managed by a single AD DS forest, if it is acceptable for the members of the Domain/Enterprise Admins groups to be able to acquire full access on resources residing in that forest.


Mind, that in an AD infrastructure, a forest is a security boundary, not a domain. A malicious Domain Administrator in any domain in a forest can hijack the Enterprise Admins group relatively easy. Learn more about it:
What Are Forests?
The Forest Is Under Control. Taking over the entire Active Directory forest
The Most Common Active Directory Security Issues and What You Can Do to Fix Them


How to define security tiers?

Every AD DS forest has a security tier, which we shall call “Tier 0”. This tier includes domain controllers, Domain/Enterprise/Schema admins groups, built-in Administrator group, Group Policy Creator Owners group etc. — basically, everything, which is not assigned to any other tier yet. Only members of Domain/Enterprise Admins groups should be able to modify Tier 0 objects.

In the real world, we do not erect AD DS domains to just leave them as is – we populate them with servers, users, workstations, DFS shares and other network resources. In medium and large enterprises, different people are responsible for different network services which usually means that you have at least one servers team, a help desk team etc. Of course, following the least privileges principle, all these people should not mess with each other’s resources, meaning helpdesk are not to configure servers and servers team should not have administrative permissions at users’ workstations.

How does one achieve that? By carefully assessing their resources and determining to which security tier would be best to assign each object.
I usually designate tiers as follows:

  • Tier 0 – domain controllers, Domain/Enterprise/Schema admins groups, built-in Administrator group, Group Policy Creator Owners group + everything not defined in other tiers.
  • Tier 1 – A set of servers/resources to which only a selected group of server administrators should have access.
  • Tier 2 – Common servers, accessible by all server administrators.
  • Tier 3 – Workstations and other objects managed by HelpDesk.

Some might also use the following optional tiers:

  • Tier 2.5 – server local administrators.
  • Tier 3.5 – workstation local administrators.

The idea behind these optional tiers is that a machine owner with local admin rights will not be able to hijack an account which is also used to manage Active Directory objects.

You can find more on how to secure Windows network here: Pass-the-Hash (PtH), Securing Privileged Access.


For my lab, I am going to implement tiers as Organizational Units which will be named as follows:

  • Tier 0 – $Tier0OUName
  • Tier 1 – $HT.Tier1.OUName
  • Tier 2 – $HT.Tier2.OUName
  • Tier 3 – $HT.Tier3.OUName

Under Tiers 1, 2 and 3, I’ll organize a hierarchy to place computer, user and group objects into respective OUs. I will also place Tier 1 and Tier 2 OUs into a single OU for the sake of applying Group Policy to all my servers at once – the name of this OU is $Tiers12CombinedOUName.
There will be no OUs under the Tier 0 OU – due to the small number of objects of this type, I am cool with all them hanging together. Besides, there ARE several Tier 0 organizational units and containers already: everything not covered by other 3 tiers, including Domain Controllers OU and Users/Computers containers.

OK, let’s create the hierarchy:


WTF is this “-f” technique and why not double quotes? This is the format operator and you can learn more about it here. Why not to use double quotes? Jonathan Angliss and Warren Frame have great coverage of this topic.




By default, in a fresh AD DS installation, any authenticated user can join up to ten computers into the domain — computer objects will be created in the default “Computers” container. While some might find this very convenient, the best practice is to manage and restrict such ability, because:

  1. You cannot assign group policies to the default Computers container, meaning those computers will receive only GPOs linked to the root of the domain.
  2. The lack of proper delegation: You cannot distinguish servers from workstations in that container and you certainly would like to, since usually we delegate different permissions to those computer types.

The same goes for the “Users” container, where users and groups are created by default.

The common approach to deal with that is to set the ms-DS-MachineAccountQuota attribute to 0, preventing semi-automatic computer object creation. But it has no effect on user and group objects.

With the commands below, we shall:

  1. Redirect computer, group and user creation operations to the custom OU.
  2. Forbid chaotic object creation by setting explicitly deny access control entry (ACE) at the OU. From there on, only delegated administrators will be able to create accounts, and they will be able to do it only in organizational units where they have appropriate permissions, meaning a HelpDesk administrator will not be able to place a workstation object along with servers.
  3. Implement the classic approach to restrict computer creation operations by modifying the ms-DS-MachineAccountQuota attribute.

What happens here? The first command is obvious: we create an Active Directory OU. Then we set the Active Directory root as our current location. Why do we need it? Because *-Acl cmdlets can work with drives only, be it classic disk volumes or virtual drives as the one we have here for Active Directory.

Then we have several long commands with magic numbers inside. Let’s split these long rows to better understand what’s going on:

Modifying Access Rules

First, what we see here, is a Get-Acl cmdlet: We need it because Set-Acl cmdlet cannot change access control lists but only rewrite them with other objects of the System.Security.AccessControl.DirectoryObjectSecurity class. Therefore, we must create such object with all required access control entries separately. And when I’m saying “required”, I talk not only about new deny entries: the new object must contain all entries which are already set on the OU.
Get-Acl allows us to achieve this lazily, returning the object of the required type, containing all current access control entries. Mind that we do not store this object in a variable – instead we pass it down to the pipeline, to the ForEach-Object cycle (% is a standard alias for the ForEach-Object cmdlet). We need the cycle, because we will perform two operations on that ACL object and we cannot pipeline just to a scriptblock. Another solution would be to store the ACL object in a variable.
In the cycle, we perform the following:

  1. Add a new access rule into the ACL object.
  2. Write this modified object as a new access control list back into the OU properties with help of the Set-Acl cmdlet.

Learn the difference between “ForEach-Object” and “foreach” in this great blog by Boe Prox!


The Set-Acl command is quite straightforward, while the command just above it, is not: There we execute AddAccessRule method which adds an access rule (or access control entry) into the ACL object. The access rule is, of course, an object too — its class is System.DirectoryServices.ActiveDirectoryAccessRule. We construct the object using New-Object cmdlet. Each object in .NET (and PowerShell uses .NET objects since it is written in .NET) has a constructor with at least one overload. An overload defines what data you must use to create an instance of an object. You can find overloads for ActiveDirectoryAccessRule here.

I decided to use the most specific overload, the last one. For this overload we must define a security principal to which the rule will be applied, a set of rights which the security principal will have by this rule, whether those rights will be allowed or denied, to which objects the rule will be applied, will the rule be inherited or not and objects of which type will inherit it.
As you see, I use “CreateChild, Self, WriteProperty, DeleteTree, Delete” as the set of rights. Than I specify that those rights must be denied and should propagate to all objects under that OU and applied to the OU itself. Slightly more complex parts are those two GUIDs 00000000-0000-0000-0000-000000000000 — this means “objects of any type”. I append the GUIDs with the type decorator to convert them from strings into GUID-class objects on the fly.

Finally, to the first argument of ActiveDirectoryAccessRule creation command: Here we see another New-Object cmdlet. Why? Because the argument accepts objects of IdentityReference class only, therefore we cannot just type “Everyone” here. So how do we apply the rule to everyone? IdentityReference class is the base class for the NTAccount and SecurityIdentifier classes. Since “Everyone” is not a real account, we must use one of the SecurityIdentifier class constructors. Since “Everyone” is a well-known security principal, we’ll just use this one.
Thanks to the WellKnownSidType enumeration, we do not need to know the actual SID which stands behind the “Everyone” security principal — we’ll just use “WorldSid” as the value which represents the required principal. I left the second parameter as NULL since it is ignored for the most of well-known security identifiers.

Combining all this together we get the command shown above. Oh, and that semi-colon before the Set-Acl command is very important — it separates two commands when they are written in a single line. But useless when line breaks are used.

Redirecting Containers

The next command in much, much simpler:

It consists of a single Set-ADObject cmdlet call which removes an element from multi-valued attribute wellKnownObjects and adds the different one back. wellKnownObjects attribute contains pointers to important Active Directory containers. Two of them are the default users container and the default computers container. Using this article, you can determine that we change the users container pointer here, because “A9D1CA15768811D1ADED00C04FD8D5CD” means GUID_USERS_CONTAINER_W.

The first (-Remove) parameter removes the current value. Since initially we do not know what this is, we request it from the $Domain object which we created at the beginning of this blog post using Get-ADDomain cmdlet. We concatenate two strings using the same “format operator” technics as discussed above. The resulted string is the one we must remove from the attribute.
Next, we add the new pointer, which points to the Redirected OU, which we created a couple of commands ago. No rocket science here, just two nested string concatenations.

The next Set-ADObject call does the same but for the Computers container.
Basically, what we do here is this, but in PowerShell.


Why do I explicitly point to the PDC emulator when writing the wellKnownObjects attribute back? Because the modification of this attribute actually takes place on the PDC emulator and you’ll receive an error “A referral was returned from the server”, if you try to run this command against non-PDC-e domain controller.


The last Set-ADObject cmdlet just ensures that even if wellKnownObjects parameters will be restored to the default settings, authenticated users still wouldn’t be able to join computers into the domain w/o pre-creating its accounts.

DNS Reverse Lookup

At last, the least important setting in this whole blog series: reverse lookup DNS-zone. It does not do anything useful, just makes your network traces easier for reading and understanding — you will see DNS-names there, instead of IP-addresses.
Create the reverse lookup zone using the following command:
Add-DnsServerPrimaryZone -Name ('{0}' -f $ReverseDNSZoneName) -ReplicationScope Forest -DynamicUpdate Secure

While I have a single subnet in this lab (at least for now), that command would be enough for me. Your environment, especially the production one, might be much more complex — create additional DNS zones if needed.


Mind that the subnet address in the $ReverseDNSZoneName variable is written in the reverse order. You can found more about “” suffix here.

Leave a Reply

Your email address will not be published. Required fields are marked *