Active Directory (AD) is at the heart of just about all modern organizations, both small and large. Microsoft first introduced AD with Windows 2000, where it replaced the domain structures previously implemented with Windows NT.
This chapter looks at how you can use PowerShell 7 to install, configure, and manage AD, as follows:
Since introducing Active Directory, Microsoft has expanded it to include a number of separate features you can install, as follows:
A full study of all these aspects could take up an entire book. This chapter covers the AD DS component and refers to it simply as “AD.”
Active Directory consists of a logical structure of forests and domains (which contain users and computers and other objects). AD also has a physical structure including AD sites, subnets, and replication partners. The physical architecture is transparent to end users; AD clients “just work,” via the magic of DNS name resolution and AD replication.
To enable administrators to deploy and manage AD, the Microsoft AD team has created two modules. You use commands in the AD Deployment module to build your forests and domains. You use the commands in the Active Directory module to manage the contents of the AD database (that is, managing users, computers, and so on).
In AD, a forest contains one or more domains in a hierarchy with a contiguous namespace (each child domain has a unique name plus the name of its parent). This contiguous namespace is known as a tree. A forest usually consists of a single tree but can contain multiple trees. The forest is a fundamental component of AD and is a key security boundary.
A domain is effectively a collection of objects including users, computers, groups, and so on. Domains enable you to support different sized organizations from small to large and globally distributed organizations. Best practice calls for a simple hierarchy with one or at most two levels of domains. This chapter demonstrates how to create and configure parent and child domains.
Each forest has a single forest root domain. That domain can have one or more subdomains, which in turn can contain subdomains. This allows you to have a forest with a forest root name; for example,
Reskit.Org
with a child domain such as
UK.Reskit.Org
.
Note that best AD naming practice suggests you name your AD forest root as a delegation of your registered Internet name. For example, if your organization has a registered name of
Reskit.Org
, then the AD should be named something like
AD.Reskit.Org
or
Corp.Reskit.Org
. That being said, the example code in this book uses shorter AD names, to produce shorter and simpler code. And although longer forest/domain names make scripts a bit easier to read and code, they also impose a small performance hit: every DNS/LDAP query is that little bit longer and the Distinguished Name of every object in the AD database is that little bit larger.
Best practice calls for each forest to be a single tree. But AD does allow you to add domain trees to a forest. This feature, first introduced with Windows Server 2000, was intended to assist large decentralized organizations. For example, you could add a separate domain tree, say
Kapoho.Com
, to the
Reskit.Org
forest. This would mean having two domain trees in a single AD Forest. You might consider this to support a subsidiary that needs a brand and email identity separate from the parent organization.
Placing noncontiguous namespace trees in a single forest is not a best‐practice solution, however, because AD has no prune‐and‐graft feature to enable you to prune a domain or domain tree from one forest and graft it into another, unrelated forest. Best practice, and a far simpler method, is to just deploy two independent forests and then connect them with a cross‐forest trust. These trusts are easy to remove as part of a company sale and allow the buyer to establish and utilize a new cross‐forest trust. This chapter also demonstrates how to create a cross‐forest trust.
Every domain in your forest has at least one domain controller (DC) and preferably more. A domain controller is a Windows Server system with the AD DS feature installed and configured that authenticates computers and users. DCs also provide user and computer Group Policy to domain client computers. DCs in each domain replicate with others so that any DC in a domain can authenticate a user or a computer. This chapter shows how to install a replica DC in a domain.
Within each domain you can define users and computers to represent individual computer users and the systems they access. AD enables an AD user to log on securely to any AD computer within the AD domain, subject to permissions. You can create AD groups, which can contain computers, users, and other groups. AD groups are invaluable for assigning permissions to resources in your domain as well as for delegating permissions in larger organizations. You can use organizational units to group users, computers, and group objects. This chapter shows how you can manage the user, group, and organizational unit objects.
AD is a complex subject. There are important aspects to AD that this book does not have space to cover, such as sites and subnets, AD replication, Global Catalog, and more. A great book on the wider subject is Brian Desmond's Active Directory: Designing, Deploying, and Running Active Directory (O'Reilly, 5th ed., 2013). Also, take a look at docs.microsoft.com/en-us/windows-server/identity/ad-ds/active-directory-domain-services for more information on AD.
Group Policy is an AD feature that enables you to define and deploy rich policies to configure a user or computer automatically. This book does not really cover Group Policy. An excellent book on the subject is Jeremy Moskowitz's Group Policy: Fundamentals, Security, and the Managed Desktop (Sybex, 3rd ed., 2015).
In this chapter, you use PowerShell 7 to build a forest with two domains (one of which has two DCs) and a second forest with one domain. You then create a cross‐forest trust between these two forests. Finally, you manage user, computer, and group objects and see how to add AD users to the domain with a comma‐separated value (CSV) file.
In this chapter, you use PowerShell 7 to install and manage Active Directory. This chapter makes use of the following host systems and forests/domains:
Reskit.Org
.UK.Reskit.Org
. Once you complete the promotion of this server, the host name becomes
UKDC1.UK.Reskit.Org
.Kapoho.Com
forest/domain to demonstrate establishing a cross‐forest trust.Figure 3.1 shows the systems you use in this chapter and the two forests and their respective domains.
Figure 3.1: Forests, domains, and hosts used in this chapter
If you are using the Reskit build scripts (from github.com/doctordns/ReskitBuildScripts) to deploy your VMs, ensure you wait until
DC1
is fully deployed as a DC before building
DC2
. The build scripts use setup XML for deploying
DC2
,
UKDC1
, and
KAPDC1
that require the existence of the
Reskit.Org
forest.
Note that each host should have PowerShell 7, VS Code, and the Cascadia Code font installed. You can do that manually, using the scripts from Chapter 1, “Setting Up a PowerShell 7 Environment.”
To establish your first AD forest, you need to create a first domain controller in the forest. This DC is known as the forest root domain controller. Upon creation, this DC holds all the forest and domain Flexible Single Master Operation (FSMO) roles.
FSMO roles designate one specific domain controller to be the master for certain forest‐wide or domain‐wide operations. For example, the Schema Master FSMO role holder, initially the forest root DC, is where any AD Schema changes are first made. If you update your AD Schema, the tools you use make the updates on this DC. If a given FSMO role holder is offline, certain operations cannot take place until you either bring the role holder back online or move the FSMO role to another online DC. For more details on FSMO roles, see techgenix.com/fsmo-roles-in-active-directory/.
To promote
DC1
to a domain controller in a new domain, you log on to the
DC1
server as a local administrator. Then you run the PowerShell code shown in this section. Once you have installed AD, you need to reboot the server and log back in using the username
RESKIT\Administrator
along with the password you used when you installed
DC1
(such as
Pa$$w0rd
or whatever password you chose to use for
DC1
's local administrator).
In this section you create the forest root domain controller in a new domain/forest. You perform this on
DC1.Reskit.Org
, a Windows Server 2019 Data Center workgroup host with the Desktop Experience option installed and a working Internet connection.
You also need to have installed PowerShell 7 on this host (and possibly more tools) using the scripts in Chapter 1.
The Server Manager module does not work natively in PowerShell 7, but you can use it using the Windows PowerShell compatibility solution discussed in Chapter 2, “PowerShell 7 Compatibility with Windows PowerShell.” To access the commands in the module, you first import it manually using the
Import‐Module
command.
# 1. Explicitly Load the Server Manager Module
Import-Module ServerManager -WarningAction SilentlyContinue
With this command, you import the Server Manager module and avoid generating the warning message that
Import‐Module
generates. Once imported, the commands in the module are available for use.
AD
Domain Services FeatureWith the Server Manager module imported, you can now add the AD Domain Services feature to
DC1
. Note that doing so does not configure
DC1
as a domain controller. Rather, it installs the components that enable you to install a DC in whatever role is appropriate (as the first DC in a forest, the first DC in a child domain, or a DC in an existing domain).
Use the
Install‐WindowsFeature
command to add the necessary components to the server.
# 2. Install the AD Domain Services feature and management tools
$FEATUREHT = @{
Name = 'AD-Domain-Services'
IncludeManagementTools = $True
WarningAction = 'SilentlyContinue'
}
Install-WindowsFeature @FEATUREHT
This code produces the output shown in Figure 3.2.
Figure 3.2: Installing AD DS Domain Services
As you can see in the figure, you do not need to reboot the server before continuing.
AD DS
Deployment Module ExplicitlyIn the previous step, you installed the
ADDSDeployment
module as part of installing the
AD‐Domain‐Services
feature. This module is not supported by PowerShell 7 natively but does work using the compatibility mechanism described in Chapter 2. You load the module with the
Import‐Module
command.
# 3. Import the AD DS Deployment Module
Import-Module -Name ADDSDeployment -WarningAction SilentlyContinue
Because this module is supported only via the Windows PowerShell compatibility mechanism, importing the module generates by default a warning message explaining that the module has been loaded using the compatibility mechanism. But this is a message you can safely ignore. To avoid this message, you set the value of
WarningAction
to
SilentlyContinue
.
With the AD DS feature installed, you create a new AD forest by promoting your server,
DC1
, to be a DC in a new forest/domain,
Reskit.Org
. Since this is the first DC in the domain, the DC becomes the forest root DC. You initiate the promotion process by performing the following:
# 4. Install Forest Root Domain and DC
$ADINSTALLHT = @{
String = 'Pa$$w0rd'
AsPlainText = $True
Force = $True
}
$SECUREPW = ConvertTo-SecureString @ADINSTALLHT
$ADHT = @{
DomainName = 'Reskit.Org' # Forest Root
SafeModeAdministratorPassword = $SECUREPW
InstallDNS = $True
DomainMode = 'WinThreshold' # latest
ForestMode = 'WinThreshold' # Latest
Force = $True
NoRebootOnCompletion = $True
WarningAction = 'SilentlyContinue'
}
Install-ADDSForest @ADHT
By setting the
WarningAction
parameter to
SilentlyContinue
, the
Install‐ADDSForest
command generates no warning messages. The command normally generates a number of warning messages, but they are benign. If you are using this snippet in production, you may want to view the error messages if only to satisfy yourself that they are indeed benign in your environment.
You can see the output from this code in Figure 3.3. These commands promote
DC1
to be a DC, but you need to reboot to complete the installation. This would enable you to carry out any other necessary configuration on the DC before the required reboot. Depending on your environment, you could instead not set the
NoRebootOnCompletion
parameter and the reboot would occur automatically.
This example shows a great use of hash tables with PowerShell commands. A hash table is a set of key/value pairs that PowerShell treats as a single object. You add parameter names (keys) and their values to the hash table and then call the cmdlet passing only the hash table object. This makes the source code a lot easier to read (and is a common feature this book uses for code snippets). For more details on hash tables, see docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_hash_tables?view=powershell-7.
Figure 3.3: Promoting
DC1
To complete the installation of the new forest, you need to reboot the server. You do this using
Restart‐Computer
as follows:
# 5. Restart computer
Restart-Computer -Force
DSE
)After the server,
DC1.Reskit.Org
, has rebooted, log in to the server (as the domain administrator
Reskit\Administrator
) and examine details of the forest by viewing the Root Directory Services Entry, or Root DSE. The Root DSE is the root of your directory tree, and
Get‐ADRootDSE
provides information about a directory server.
# 6. After reboot, log back into DC1 as Reskit\Administrator
Get-ADRootDSE |
Format-Table -Property DNS*, *Functionality
You can get the Root DSE for any standards‐based LDAP server, including Windows Active Directory. You can think of the DSE as a road map of what's inside this DC and the features it supports.
An important security point is that the Root DSE is provided without requiring any authentication. If you have DCs exposed to the Internet, they provide their information anonymously. This could enable an attacker to learn more about your domain structure, and for that reason exposing DCs on the Internet is to be avoided.
As you can see in Figure 3.4, our host now has a fully qualified host name of
DC1.Reskit
.
Org
, is a DC in a Windows domain and forest, and has both domain and forest functionality set at the Windows 2016 level. There were no additional AD features in Windows Server 2019, so this setting means that all existing features are available.
Figure 3.4: Viewing the DSE
AD DS
ForestYou can use the
Get‐ADForest
command to view details of the new
Reskit.Org
forest, including forest‐level FSMOs, the servers presently acting as a Global Catalog (GC), and the set of domains in the forest by using the following:
# 7. Examine ADDS forest
Get-ADForest |
Format-Table -Property *master*, global*, Domains
Figure 3.5 shows the output of this code.
Figure 3.5: Viewing the Reskit.Org forest
A GC provides access to a partial replica of all objects in a given forest and is used for lookups. Exchange Server, for example, makes heavy use of GC servers. This page provides some additional detail on the role of a GC: docs.microsoft.com/en-us/windows/win32/ad/global-catalog.
In this figure, you can see that all forest‐level FSMO roles are held by
DC1.Reskit.Org
. As you deploy more DCs, you can move the forest‐level FSMOs as needed. In general, forest‐level FSMOs should be held by DCs at your network core.
You can view details of the
Reskit.Org
domain using the
Get‐ADDomain
command. You may find that some of the default output provided by
Get‐ADDomain
is not very useful, so you can refine the information displayed as follows:
# 8. View details of the domain
Get-ADDomain |
Format-Table -Property DNS*, PDC*, *master, Replica*
You can view the output from this step in Figure 3.6. This shows that, so far, there is only one domain in the forest and one DC in that domain.
Figure 3.6: Viewing the Reskit.Org domain
In this figure, you can see that all domain‐level FSMO roles are held by
DC1.Reskit.Org
. As you deploy more DCs, you can move FSMO roles as needed. As with forest‐level FSMOs, you probably want the domain FSMOs to be held centrally. If you have multiple levels of domains, you may want to move domain‐level FSMOs “nearer” to the center of each domain. Ultimately, you need to ensure that DCs can locate and connect to all FSMO role holders.
DNS
SettingsWhen you promoted
DC1.Reskit.Org
as a DC, in “Creating a Forest Root Domain Controller,” the promotion process added a DNS server on
DC1
, created a zone for your domain, and populated a number of DNS resource records (RRs). You can view the DNS details by using the following steps:
# 9. View DNS Settings
Get-Service -Name DNS
Get-DnsServerZone
Get-DnsServerResourceRecord -ZoneName 'Reskit.Org'
As you can see in Figure 3.7, the DNS Server service is up and running on
DC1
. You can see the zones created in this DNS server, including the
Reskit.Org
DNS domain. Finally, you can see the various DNS RRs that the AD service creates and uses. If you are using multiple virtual NICs in your VM, you will see more resource records.
Figure 3.7: Viewing DNS settings and configuration
In AD, the Netlogon service writes the AD‐related DNS RRs to DNS each time the service starts and every 24 hours thereafter. You can test this self‐healing feature by stopping the Netlogon service, removing the AD‐related RRs, and then restarting the service. You should see that the necessary RRs are now restored. Of course, be careful when testing this on a production domain controller.
In “Establishing a Forest Root Domain,” you created an AD domain with a single DC. In test environments, this might be more than adequate. For production, best practice suggests having more than one DC. If you are using virtualization to host domain controller VMs, you should ensure that AD VMs are hosted on independent virtualization hosts (irrespective of the virtualization platform). This avoids a single point of failure.
In this section you add a domain‐joined server,
DC2.Reskit.Org
(built using Windows Server 2019), to the
Reskit.Org
domain. If you are using the Reskit build scripts to deploy this DC, make sure you do not build the
DC2
VM until after you have finished promoting
DC1
to be a domain controller.
As with other servers you use in this chapter, you should have installed PowerShell 7 on
DC2
. To install PowerShell 7 and optionally VS Code, you can use the scripts in Chapter 1.
You start the process of promoting
DC2
by loading the Server Manager module. This module is not supported natively by PowerShell 7. The command, however, works using the Windows PowerShell compatibility mechanism mentioned in Chapter 2.
You install it using the
Import‐Module
command.
# 1. Import the Server Manager module
Import-Module -Name ServerManager -WarningAction SilentlyContinue
By specifying the
‐WarningAction
parameter, you avoid seeing the warning message that would be generated to warn you that this module is being used in a compatibility session.
Before promoting the server
DC2.Reskit.Org
to be a DC, you need to ensure that another DC is online and can be reached. If this fails, it means that promotion is not going to succeed. Tests you can run prior to promoting the server include these:
# 2. Check DC1 can be resolved and can be reached from DC2
Resolve-DnsName -Name DC1.Reskit.Org -Type A
Test-NetConnection -ComputerName DC1.Reskit.Org -Port 445
Test-NetConnection -ComputerName DC1.Reskit.Org -Port 389
You can see the output of these commands in Figure 3.8.
Figure 3.8: Testing connectivity with
DC1
In the figure, you can see that
DC2
is able to connect to resolve
DC1
's IP address and then connect over ports 445 and 389. Typical issues are related to IP addressing/configuration and DNS. And if you are using virtualization, the virtualization network configuration can also be an issue.
AD DS
Features on
DC2
Another prerequisite step is getting the relevant features onto your host, using
Install‐WindowsFeature
as follows:
# 4. Add the AD DS features on DC2
Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools
Running the
Install‐WindowsFeature
cmdlet generates the output you see in Figure 3.9.
Figure 3.9: Installing Windows features
DC2
To promote
DC2
, you run the following commands:
4. Promote DC2
Import-Module -Name ADDSDeployment -WarningAction SilentlyContinue
$URK = "Administrator@Reskit.Org"
$PW = 'Pa$$w0rd'
$PSS = ConvertTo-SecureString -String $PW -AsPlainText -Force
$CredRK = [PSCredential]::New($URK,$PSS)
$INSTALLHT = @{
DomainName = 'Reskit.Org'
SafeModeAdministratorPassword = $PSS
SiteName = 'Default-First-Site-Name'
NoRebootOnCompletion = $true
InstallDNS = $false
Credential = $CredRK
Force = $true
}
Install-ADDSDomainController @INSTALLHT | Out-Null
These commands promote
DC2
to be a domain controller. Once the command has completed its work, you must reboot the system to complete the promotion process. The installation command you use to promote
DC2
can generate warning messages that refer to Knowledge Base article 942564. In general, these are benign errors, and you can ignore them.
DC2
To finalize the promotion process, restart
DC2
, like this:
# 5. Reboot manually
Restart-Computer -Force
This command restarts the system. If you had unsaved work, possibly in another window, you may have lost it.
DCs
in Reskit.Org DomainAfter the reboot process has completed, you can log in to
DC2
as the domain administrator. One of the first things you can do is to check to see which computers are now in the Domain Controllers organizational unit, with these commands:
# 6. Check DCs in Reskit.Org
$SB = 'OU=Domain Controllers,DC=Reskit,DC=Org'
Get-ADComputer -Filter * -SearchBase $SB |
Format-Table -Property DNSHostname, Enabled
The output, which you can see in Figure 3.10, shows that you now have two DCs in the Domain Controllers OU and by implication in the domain.
Figure 3.10: Viewing DCs in the
Reskit.Org
domain
In addition to verifying that you have a second DC, another useful test is to view the AD's domain details using
Get‐ADDomain
, like this:
# 7. View Reskit.Org Forest
Get-ADDomain |
Format-Table -Property Forest, Name, Replica*
These commands return the name of the forest, the name of the domain, and the DCs in the domain, as shown in Figure 3.11.
Figure 3.11: Viewing DNS settings and configuration
As you can see in the figure, you now have a second working domain controller in the
Reskit.Org
domain. There are a variety of other tests you might want to do before proceeding, including checking the various network ports on
DC2
, validating DNS entries, and ensuring that replication is happening between the two DCs.
For many organizations, a single domain forest with multiple DCs is adequate and has the benefit of being simple to manage. But in some cases, having one or more child domains can be advantageous. For example, you might have two parts of your organization that are geographically distant. An advantage of using a child domain is that some parts of AD are not replicated between domains, which can reduce WAN usage. For geographically dispersed organizations, you also have the option of using a single domain and placing DCs in separate AD sites.
In this section, you create a new child domain,
UK.Reskit.Org
, as a child of the
Reskit.Org
domain you set up earlier in this chapter.
In this section you extend the
Reskit.Org
forest (set up in “Establishing a Forest Root Domain”) by adding a child domain named
UK.Reskit.Org
with a single DC, named
UKDC1.UK.Reskit.Org.
This host is built using Windows Server 2019. You also need to install and configure PowerShell 7 on the host and optionally install VS Code using the scripts in Chapter 1.
To add the AD DS tools onto the host, you need to use the Server Manager module. This module is one that
Import‐Module
detects as not supported natively in PowerShell 7 and makes use of the compatibility solution described in Chapter 2. You load the module as follows:
# 1. Import the ServerManager module
Import-Module ServerManager -WarningAction SilentlyContinue
This command loads the Server Manager module using the Windows PowerShell compatibility mechanism discussed in Chapter 2. It creates a PowerShell remoting session, loads the module in that remote session, and creates local proxy functions for the commands in the Server Manager module. You can view the remoting session details using the
Get‐PSSession
command.
DC1
Can Be ResolvedTo create the new subdomain, your server (
UKDC1
) needs to have connectivity to the parent domain. In particular, this host needs access to the Domain Naming Master FSMO forest role holder in the parent domain. If you are following the steps so far in this chapter, that server would be
DC1.Reskit.Org
. Before promoting the server, it is useful to test that you can connect to that server, using
Test‐NetConnection
as follows:
# 2. Check DC1 can be resolved and can be reached over 445 and 389 from UKDC1
Resolve-DnsName -Name DC1.Reskit.Org -Type A
Test-NetConnection -ComputerName DC1.Reskit.Org -Port 445
Test-NetConnection -ComputerName DC1.Reskit.Org -Port 389
You can see the output from these commands in Figure 3.12. It shows that you can resolve the parent DC and can connect to it via both port 445 and port 389.
Figure 3.12: Verifying connectivity to
DC1
If for any reason you cannot connect to the parent domain, you need to troubleshoot and resolve that issue. Without connectivity to the parent, the new domain cannot be created. Typical issues with connection failure include network configuration and DNS. And in production, ensure that you have and use the correct credentials.
AD DS
Features to
UKDC1
Before you can make
UKDC1
a domain controller, you must add the AD Domain Services features to the server, as follows:
# 3. Add the AD DS features on UKDC1
$Features = 'AD-Domain-Services'
Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools
You can view the output from installing this feature in Figure 3.13.
In production, the output from the
Install‐WindowsFeature
command is probably not very helpful. If anything, it's more useful when it fails. When it succeeds, you can largely ignore the output and proceed to the next step. If it fails, you need to discover why and resolve the issue. Once the issue is resolved, you can retry this step.
Figure 3.13: Adding features to
UKDC1
You create the new domain as follows using the
Install‐ADDSDomain
command, like this:
# 4. Create New Domain
Import-Module -Name ADDSDeployment -WarningAction SilentlyContinue
$URK = "Administrator@Reskit.Org"
$PW = 'Pa$$w0rd'
$PSS = ConvertTo-SecureString -String $PW -AsPlainText -Force
$CredRK = [PSCredential]::New($URK,$PSS)
$INSTALLHT = @{
NewDomainName = 'UK'
ParentDomainName = 'Reskit.Org'
DomainType = 'ChildDomain'
SafeModeAdministratorPassword = $PSS
ReplicationSourceDC = 'DC1.Reskit.Org'
Credential = $CredRK
SiteName = 'Default-First-Site-Name'
InstallDNS = $false
Force = $true
}
Install-ADDSDomain @INSTALLHT
These commands promote
UKDC1
to be a new DC in a child domain. Once the installation is complete, the machine reboots to complete the configuration of
UKDC1
.
AD
ForestAfter the server reboots, you can log in. You have the option of logging in as either
Reskit\Administrator
or
UK\Administrator
. That demonstrates the ability to log into a child domain with credentials from another domain in the forest.
If you log in as
UK\Administrator
, you can view the details of the
UK.Reskit.Org
domain as follows:
# 5. Look at AD forest
Get-ADForest -Server UKDC1.UK.Reskit.Org
It's worth taking a moment to study the output from this step, which you can see in Figure 3.14.
Figure 3.14: Viewing the
Reskit
forest
In this figure, you see that the forest now has two domains (
Reskit.Org
and
UK.Reskit.Org
). Additionally, both forest‐wide FSMO roles are held by
DC1.Reskit.Org
(in the parent domain).
You can also view the details of the newly created
UK.Reskit.Org
domain using the
Get‐ADDomain
command, as follows:
# 6. Look at the UK domain
Get-ADDomain -Server UKDC1.UK.Reskit.Org
Figure 3.15 shows the details for the
UK.Reskit.Org
domain.
This output provides more details about the newly created child domain. You can see that the domain‐wide FSMO role holders all point to this newly created DC. In production you would want a second DC (or more as appropriate) and may need to move the FSMO roles. This output also shows further domain‐wide configuration information.
Once you have completed these steps, you have a working child domain (
UK.Reskit.Org
) in addition to the parent domain (
Reskit.Org
).
Figure 3.15: Viewing the child domain information
In this chapter thus far, you have installed a forest with two domains, namely,
Reskit.Org
and the child domain
UK.Reskit.Org
. In most organizations a one‐ or two‐level domain structure is best practice. That means either a single‐domain forest or a forest with a single root domain and one or more child domains.
Having a two‐level domain structure with multiple child domains has some advantages for large distributed organizations. In particular, separate child domains can help to avoid replication between domains (which typically means additional WAN traffic).
While you can have multiple domain trees within a single forest, that is usually not a good idea. In most cases, the desire for multiple noncontiguous domain trees arises from the need of different parts of the organization to have their own domains (and therefore email and FQDN server names). For example, if a large conglomerate bought a company with a strong brand identity, it might make commercial sense to keep that separate with a separate domain tree.
However, because you cannot prune and graft parts of your AD forest between other external forests, when you buy or sell that strongly named subsidiary, a buyer might face challenges integrating the old attached domain/forest into the new environment. A much better approach is to create two separate and independent forests and then implement a cross‐forest trust. A cross‐forest trust means that you can use accounts in one domain in the access control list (ACL) of a resource in a different domain to support resource access between different forests.
In this section, you create a new forest (
Kapoho.Com
) on a newly installed server (
KAPDC1
). You create this server initially as a workgroup server and then promote it to be a DC in a new forest. Once this new forest is created, you can create and leverage the cross‐forest trust.
In this section, you create a cross‐forest trust between the
Kapoho.Com
and
Reskit.Org
forests and then use this trust to update ACLs to facilitate cross‐forest resource access. To achieve this, you need to have created the domain controllers in the two forests (
DC1.Reskit.Or
g and
KAPDC1.Kapoho.Org
). You run the steps in this section on
KAPDC1
. As with other servers, you need to install PowerShell 7 and optionally VS Code on this server. You can use the scripts in Chapter 1 to do so.
To add the AD DS tools onto the host, you need to use the Server Manager module. This module is one that
Import‐Module
detects as not supported natively in PowerShell 7 and makes use of the compatibility solution described in Chapter 2. You load the module using
Import‐Module
as follows:
# 1. Import the ServerManager module on KAPDC1
Import-Module ServerManager -WarningAction SilentlyContinue
This command loads the module using the Windows PowerShell compatibility mechanism described in Chapter 2. In doing so, it creates a PowerShell remoting session to the local host, loads the module in that remote session, and creates local proxy functions for the commands in the Server Manager module. You can view the remoting session details by using the
Get‐PSSession
command.
AD
Domain Services Feature and Management ToolsYou can now install the AD Domain Services feature, including the necessary management tools, as follows:
# 2. Install the AD Domain Services feature and Management Tools
$Features = 'AD-Domain-Services'
Install-WindowsFeature -Name $Features -IncludeManagementTools
This step produces the output you can see in Figure 3.16.
Figure 3.16: Installing the AD DS feature
This command adds the AD Domain Services to the host but does not promote the host to be a DC. You promote
KAPDC1
to be a new DC in a separate step (“Promoting KAPDC1”).
DC1
To create the cross‐forest trust,
KAPDC1.Kapoho.Com
needs to be able to connect with
DC1.Reskit.Org
. So, before trying to create the trust, you test this connectivity between the two DCs, as follows:
# 3. Test Network Connectivity with DC1
Test-NetConnection -ComputerName DC1
Assuming
DC1
and
KAPDC1
are both online and working, the output you see should look like Figure 3.17.
Figure 3.17: Testing network connectivity
You can extend these tests to verify full network connectivity using relevant ports such as 389 and 445.
AD DS
Deployment ModuleTo promote
KAPDC1
to be a DC in the
Kapoho.Com
domain, you need to import the AD DS Deployment module as follows:
# 4. Import the AD DS Deployment Module
Import-Module -Name ADDSDeployment -WarningAction SilentlyContinue
This command imports the module using the Windows Compatibility mechanism described in Chapter 2. Once you load the module, you can use
Get‐Command
to view the proxy commands created.
KAPDC1
Next, you promote
KAPDC1
to be a forest root DC in the
Kapoho.Com
forest/domain, as follows:
# 5. Promote KAPDC1 to be DC in its own forest
$ADINSTALLHT = @{
String = 'Pa$$w0rd'
AsPlainText = $True
Force = $True
}
$SECUREPW = ConvertTo-SecureString @ADINSTALLHT
$ADINSTALLHT = @{
DomainName = 'Kapoho.Com' # Forest Root
SafeModeAdministratorPassword = $SecurePW
InstallDNS = $True
DomainMode = 'WinThreshold' # latest
ForestMode = 'WinThreshold' # Latest
Force = $True
WarningAction = 'SilentlyContinue'
}
Install-ADDSForest @ADINSTALLHT | Out-Null
This step produces no output. Because you did not use the
‐NoRebootOnCompletion
parameter, once the promotion process has completed, the cmdlet reboots to finalize the server to be a DC. Specifying this parameter means that no reboot happens automatically; thus, you can control when the reboot happens. In production, you might be doing other operations in parallel with promoting the server, such as copying some files or configuring applications on the server. This parameter allows you to reboot when appropriate. Note that until you reboot, the new domain is not available.
After rebooting, you can log in as the administrator in the new domain using either Administrator@Kapoho.Com or Kapoho\Administrator. Once logged in, it is useful to review and verify details of the new forest. You can view the forest details as follows:
# 6. View Kapoho.Com Forest Details
Get-ADForest
The output from this command, which you see in Figure 3.18, shows key details about the new forest. It is useful to validate that the DC has been created and promoted as you expect and that the relevant FSMO roles are set.
Figure 3.18: Viewing details of the Kapoho.Com forest
With the steps so far in this section, you have created another forest and domain. At present, there is no relationship between the two forests; thus, there is no cross‐forest resource access yet. There are a number of further steps you need to take to implement a cross‐forest trust.
DNS
to Resolve Reskit.Org from
KAPDC1
With two independent forests, you need to ensure that DNS clients in both forests are able to resolve hosts in the other forest. A simple way to do that is to create a conditional DNS forwarder on each forest's DNS server(s). That way, a DHCP client in
Reskit.Org
can resolve details of hosts in the
Kapoho.Com
zone by way of the forwarder. To do this locally on
KAPDC1
, you run the following:
# 7. Adjust DNS on KAPDC1 to resolve Reskit.Org from DC1
$CFHT = @{
Name = 'Reskit.Org'
MasterServers = '10.10.10.10'
Passthru = $True
}
Add-DnsServerConditionalForwarderZone @CFHT
You can see the output of this command in Figure 3.19.
By performing this step, you configure the DNS server on
KAPDC1
to resolve
Reskit.Org
‐related addresses by forwarding requests to the DNS server at
10.10.10.10
, which is
DC1.Reskit.Org
.
Figure 3.19: Adding a DNS conditional forwarder
This step demonstrates one of two methods you can deploy to implement cross‐forest DNS lookups. The other method is to use stub zones. Each method has some advantages and disadvantages; for more information, see this article: winadmin.org/questions-answers/2-windows/2-windows/5-what-is-the-difference-between-stub-zone-and-conditional-forwarders-when-are-they-used-2.
DNS
ForwardingWith conditional DNS forwarding set up on
KAPDC1
, you test it using
Resolve‐DNSName
. To attempt to resolve the IP address of the
DC1.Reskit.Org
server, enter the following:
# 8. Test Conditional Forwarding
Resolve-DNSName -Name DC1.Reskit.Org -Type A
As you can see in the output in Figure 3.20,
DC1
has an A resource record for the IPv4 address of
DC1
.
Figure 3.20: Testing conditional DNS forwarding
With the conditional forwarder set up on
KAPDC1.Kapoho.Com
, you can now set up and validate a forwarder on
DC1.Reskit.Org
to forward queries for
Kapoho.Com
to
KAPDC1.Kapoho.Com
, as follows:
# 9. Create a Script Block to Add Conditional Forwarder on DC1
$SB = {
# Add CF zone
$CFHT = @{
Name = 'Kapoho.Com'
MasterServers = '10.10.10.131'
}
Add-DnsServerConditionalForwarderZone @CFHT
# Test it
Resolve-DNSName -Name KAPDC1.Kapoho.Com | Format-Table
}
These instructions create a script block that you use to add a conditional forwarder on
DC1
to the DNS service on
KAPDC1
.
DC1
Because
DC1
is in a separate AD forest, you have to create a credentials object you can use to run the script block on
DC1
, as follows:
# 10. Create Credentials to Run A Command on DC1
$URK = 'Reskit\Administrator'
$PRK = ConvertTo-SecureString 'Pa$$w0rd' -AsPlainText -Force
$CREDRK = [PSCredential]::New($URK,$PRK)
WinRM
To run the script block remotely, on a system in another Kerberos realm, you need to adjust the WinRM service. To enable WinRM to run the script block, you need to update the Trusted Hosts list. This list defines which hosts you can connect to (without using Kerberos). You do this as follows:
# 11. Set WinRM
$PATH = 'WSMan:\localhost\Client\TrustedHosts'
Set-Item -Path $PATH -Value '*.Reskit.Org' -Force
After running this snippet, your system trusts any server in the
Reskit.Org
domain as being who it says it is. This is a potential security risk, and you should consider whether wildcard values like this are acceptable. Once you've demonstrated the capability for yourself, you may want to disable it on your system.
DC1
Now that you have created a script block and configured the environment, you can run the script block on the remote server as follows:
# 12. Run the Script Block on DC1
$NZHT = @{
Computername = 'DC1.Reskit.Org'
Script = $SB
Credential = $CREDRK
}
Invoke-Command @NZHT
In the output, shown in Figure 3.21, you can see that after setting up conditional forwarding, a DNS client on
DC1
is able to resolve a RR for a host in the
Kapoho.Com
domain. Depending on how you have configured Internet access and whether you have IPv6 enabled, you may see additional RRs resolved.
Figure 3.21: Setting up a conditional forwarder on DC1
The AD modules do not support creating a cross‐forest trust. To set up a cross‐forest trust, you must make use of .NET objects and their methods.
To set up the cross‐forest trust, first get the NET objects representing each of the two forests, as follows:
# 13. Get Reskit.Org and Kapoho.Com details
$Reskit = 'Reskit.Org'
$User = 'Administrator'
$UserPW = 'Pa$$w0rd'
$Type = 'System.DirectoryServices.' +
'ActiveDirectory.DirectoryContext'
$RKFHT = @{
TypeName = $Type
ArgumentList = 'Forest',$Reskit,$User,$UserPW
}
$RKF = New-Object @RKFHT
$ReskitForest =
[System.DirectoryServices.ActiveDirectory.Forest]::GetForest($RKF)
$KapohoForest =
[System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
This snippet creates variables that contain details of the two forests you are working with. These forest objects also have useful methods that are not available via PowerShell commands.
Since you store the output from these last two method calls in variables, you see no output.
The .NET namespace
System.DirectoryServices.ActiveDirectory
contains objects that represent the key AD components, including forest, domain, site, subnet, partition, and schemas. These classes have properties and methods that perform a variety of AD tasks. In many cases, these classes and methods overlap with commands in the Active Directory module. The classes do, however, provide properties and methods not available in the AD modules.
Now that you have obtained the forest details for the
Reskit.Org
forest, you can display the information as follows:
# 14. View Reskit.Org Forest Details
$ReskitForest
This produces the output shown in Figure 3.22.
Figure 3.22: Viewing the Reskit.Org forest
You can also view the details of the
Kapoho.Com
forest, using similar syntax:
# 15. View Kapoho Forest Details
$KapohoForest
The output, which you can see in Figure 3.23, is similar to that of the Reskit forest.
Figure 3.23: Viewing the Kahopo.Com forest
Now that you have the objects representing the two forests, you can establish a trust between the forests, using the
CreateTrustRelationship()
method as follows:
# 16. Establish a trust
$KapohoForest.CreateTrustRelationship($ReskitForest,"Bidirectional")
In this case, you are establishing a bidirectional trust, meaning that both domains now trust each other; you can now specify that security principals in either domain can access resources in the other domain.
ACL
of a File on
DC1
With the cross‐forest trust set up, you can now make use of the trust. In particular, the trust means you can create a file (on
DC1
) and adjust the ACL to allow access both from members of the
Reskit.Org
forest and from users in the
Kapoho.Com
forest.
The simplest way to set ACLs on NFTS files and folders is to use the external
NTFSSecurity
module. You download this module from the PowerShell Gallery using the
Install‐Module
command. After installing the module, you can create a file (on
DC1
) and configure the ACL to enable cross‐forest access.
You do this by first creating a script block, as follows:
# 17. Create SB to Adjust ACL on DC1
$SB2 = {
# Ensure NTFSSecurity module is loaded on DC1
Install-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
# Create a file in C:\Foo
'XFT Test' | Out-File -FilePath 'C:\Foo\XFTTEST.Txt'
# Test ACL
Get-NTFSaccess -Path C:\Foo\XFTTEST.Txt | Format-Table
# Add Kapoho\Administrators into ACL for this file
$NTHT = @{
Path = 'C:\Foo\XFTTEST.TXT'
Account = 'Administrator@Kapoho.Com'
AccessRights = 'FullControl'
}
Add-NTFSAccess @NTHT
# Retest ACL
Get-NTFSaccess -Path C:\Foo\XFTTEST.Txt | Format-Table
}
This code snippet creates a script block (on
KAPDC1
), which creates a new file (on the remote system) and then looks at the initial ACL. After adding the
Kapoho\Administrator
to the ACL of this file, the script block retrieves and displays the updated ACL.
DC1
to Demonstrate the Cross‐Forest TrustTo view the cross‐forest trust in action, you can run the script block as follows:
# 18. Run the ScriptBlock on DC1 To Demonstrate X-Forest Trust
$PHT = @{
ComputerName = 'DC1.Reskit.Org'
Credential = $CREDRK
ScriptBlock = $SB2
}
Invoke-Command @PHT
Note that you need to ensure that connectivity between
KAPDC1
and
DC1
. Figure 3.24 shows the output of this step. The NTFS Security cmdlets produce an extra white space in the output. You can avoid that by piping the output of, for example,
Get‐NTFSAccess
to
Format‐Table
and specifying
‐AutoSize
.
In practice, you should be using the AGDLP (account, global, domain, local permissions) approach to setting permissions (see en.wikipedia.org/wiki/AGDLP for more information on this approach). The mechanism shown in this section works well but may not be as scalable. For larger enterprises, the AGDLP approach is easier to deploy and manage.
With AGDLP, you place user and group accounts that need to access resources into global groups in each forest/domain. Then you add those global groups into Domain Local groups on each domain. Once these Domain Local groups are created, you can assign permissions to a resource in either forest based on the Domain Global groups in each forest. In this book's scenario, you create a Domain Local group in the Reskit domain and then add a global group from the Kapoho domain to it.
Figure 3.24: Updating the ACL and viewing its details
Once you have your Active Directory infrastructure built, the next step in deployment is to add objects to the directory and subsequently manage them.
There are four sets of AD objects you are most likely to use.
An AD user object represents a user account that can be used to log on and can be used in resource ACLs. An AD computer object represents a computer that can log in to the domain and onto which a user can log. AD computers can also be used in ACLs. An AD group is an account that contains other user, computer, or group objects. To simplify ACL management, you use a group in an ACL instead of individual users/computers. An organizational unit (OU) is an AD object that contains other AD objects, including other OUs. You also use OUs for two main purposes: to delegate administration and to support group policies.
You use the commands in the Active Directory module to manage the objects in your AD. This module is supported natively in PowerShell 7.
You run the code snippets in this section on the Windows Server domain controller,
DC1.
Reskit.Org. You created this server in “Establishing a Forest Root Domain” and have used it throughout this chapter.
Creating a user object requires specifying a number of parameters, which can lead to long lines of code (that can be harder to troubleshoot). To simplify the creation of AD user (and other) objects, you can use a hash table to hold the properties you want to set when creating the object.
You create this hash table by assigning values to the
$NewUserHT
variable as follows:
# 1. Create a hash table for general user attributes
$PW = 'Pa$$w0rd'
$PSS = ConvertTo-SecureString -String $PW -AsPlainText -Force
$NewUserHT = @{
AccountPassword = $PSS
Enabled = $true
PasswordNeverExpires = $true
ChangePasswordAtLogon = $false
}
This hash table contains values for all the users to be added to AD in this section.
Now that you have the basic user properties hash table set up, you extend it to include user‐specific details and then add the user to the AD as follows:
# 2. Create two users - adding to basic hash table
# First user
$NewUserHT.SamAccountName = 'ThomasL'
$NewUserHT.UserPrincipalName = 'ThomasL@reskit.Org'
$NewUserHT.Name = 'ThomasL'
$NewUserHT.DisplayName = 'Thomas Lee (IT)'
New-ADUser @NewUserHT # add first user
# Second user
$NewUserHT.SamAccountName = 'RLT'
$NewUserHT.UserPrincipalName = 'RLT@Reskit.org'
$NewUserHT.Name = 'Rebecca Lee-Tanner'
$NewUserHT.DisplayName = 'Rebecca Lee-Tanner (IT)'
New-ADUser @NewUserHT # Add second user
This snippet creates two new users in the AD. By default, these users are added to the Users container in AD. This container is not an OU.
This snippet shows some basic user settings. As noted earlier, you may want to extend the hash table to set additional properties on the user object being created.
OU
for
IT
An OU is a container object inside AD. When you added the two users to AD, it added them into the Users container. This is not helpful, as you can only apply group policies to an OU. Most organizations therefore use OUs to hold user, computer, and group objects.
It is important to note that both an OU and the Users container are container‐type objects (AD objects that can contain other objects). The difference is that you can only apply group policies to an OU. It is best practice to have your AD computers, groups, and users in OUs.
To create an OU, in this case for the Reskit IT team, you can do the following:
# 3. Create an Organizational Unit for IT
$OUHT = @{
Name = 'IT'
DisplayName = 'Reskit IT Team'
Path = 'DC=Reskit,DC=Org'
}
New-ADOrganizationalUnit @OUHT
This code creates a new OU at the top level of the Reskit.Org domain. Initially, it contains no objects.
OU
With the IT OU created, you can now move each of the two users created earlier in this section into this OU, as follows:
# 4. Move the two users into the OU
$MHT1 = @{
Identity = 'CN=ThomasL,CN=Users,DC=Reskit,DC=ORG'
TargetPath = 'OU=IT,DC=Reskit,DC=Org'
}
Move-ADObject @MHT1
$MHT2 = @{
Identity = 'CN=Rebecca Lee-Tanner,CN=Users,DC=Reskit,DC=ORG'
TargetPath = 'OU=IT,DC=Reskit,DC=Org'
}
Move-ADObject @MHT2
This snippet illustrates how to move an AD object. In this case, you move two specific users contained in the Users container into the IT OU. Once you have moved these user objects, the next time either user logs in, any OU‐specific GPOs are applied.
OU
As an alternative to creating an AD user in the Users container (and then moving the user to an appropriate OU), you can specify an OU path to the OU in which to create the new user, like this:
# 5. Create a third user directly in the IT OU
$NewUserHT.SamAccountName = 'JerryG'
$NewUserHT.UserPrincipalName = 'jerryg@reskit.org'
$NewUserHT.Description = 'Virtualization Team'
$NewUserHT.Name = 'Jerry Garcia'
$NewUserHT.DisplayName = 'Jerry Garcia (IT)'
$NewUserHT.Path = 'OU=IT,DC=Reskit,DC=Org'
$NewUserHT.PasswordNeverExpires = $true
$NewUserHT.ChangePasswordAtLogon = $false
New-ADUser @NewUserHT
You use this user in “Configuring Just Enough Administration (JEA).” In this example, you explicitly set the account password to never expire and to enable the user to log in a first time without changing passwords. That might be a security risk in a larger organization but is useful for this book.
To demonstrate removing AD objects, begin by creating two users that you will later delete, as follows:
# 6. Add two users who are then removed
# First user to be removed
$NewUserHT.SamAccountName = 'TBR1'
$NewUserHT.UserPrincipalName = 'tbr1@reskit.org'
$NewUserHT.Name = 'TBR1'
$NewUserHT.DisplayName = 'User to be removed'
$NewUserHT.Path = 'OU=IT,DC=Reskit,DC=Org'
New-ADUser @NewUserHT
# Second user to be removed
$NewUserHT.SamAccountName = 'TBR2'
$NewUserHT.UserPrincipalName = 'tbr2@reskit.org'
$NewUserHT.Name = 'TBR2'
New-ADUser @NewUserHT
This snippet creates two new users (
TBR1
and
TBR2
) in the IT OU.
With an OU and several users created, you can use
Get‐ADUser
to view the existing AD users, as follows:
# 7. See the users that exist so far
Get-ADUser -Filter * -Properties DisplayName |
Format-Table -Property Name, DisplayName, SamAccountName
You can see the output of this command in Figure 3.25. Depending on your needs, you may want to add more parameters to this display.
Figure 3.25: Viewing users in Reskit.Org
As you can see in the figure, there are not many users in this domain. Also, note that the users added by default lack a
DisplayName
property, whereas the users you added did have a value set for that property.
There are at least two ways you can remove AD objects, including AD users. The first uses a
Get | Remove
pattern. In this pattern you first get objects (using, for example,
Get‐ADUser
) and then pipe them to a command (for example,
Remove‐ADUser
) to remove the objects (in this case, users). This pattern is particularly useful interactively. You use the
Get
portion to get the specific objects you need, verify that you are getting the right objects, and only then remove them. You can do this as follows:
# 8. Remove via a Get | Remove Pattern
Get-ADUser -Identity 'CN=TBR1,OU=IT,DC=Reskit,DC=Org' |
Remove-ADUser -Confirm:$false
A second way to remove a user is to use
Remove‐ADUser
and specify the identity for the user to be removed. There are several ways to specify the identity of the object; in this case, you use the full distinguished name, as follows:
# 9. Remove user directly
$RUHT = @{
Identity = 'CN=TBR2,OU=IT,DC=Reskit,DC=Org'
Confirm = $false}
Remove-ADUser @RUHT
This demonstrates how you can remove a specific user directly. This approach is possibly quicker than using the
Get | Remove
pattern, but arguably less safe in the case of typos.
Once you create an object, whether a user, a group, or whatever, you may need to update it. You may need to update a user object with a revised phone number or office name. You can update and view a user object with the
Set‐ADUser
and
Get‐ADUser
commands, as follows:
# 10. Update and display a user
$TLHT =@{
Identity = 'ThomasL'
OfficePhone = '4416835420'
Office = 'Marin Office'
EmailAddress = 'ThomasL@Reskit.Org'
GivenName = 'Thomas'
Surname = 'Lee'
HomePage = 'Https://tfl09.blogspot.com'
}
Set-ADUser @TLHT
Get-ADUser -Identity ThomasL -Properties DisplayName, Office,
OfficePhone, EmailAddress |
Format-Table -Property DisplayName, Name, Office,
OfficePhone, EmailAddress
You can see the output of this step in Figure 3.26.
As in any script that outputs user details, you are likely to want to manage, retrieve, and view many different object properties; thus, you may want to extend the properties displayed by
Format‐Table
. Also, remember that if you want to display a property, you may need to extend the value of the
‐Properties
parameter in
Get‐ADUser
as well so as to ensure you retrieve the property in the first place before displaying it. Note that retrieving additional properties adds performance overhead. Retrieving all properties can take two to three times as long as retrieving only the default set. Best practice is to retrieve only the properties you need.
Figure 3.26: Viewing updated user details
AD
GroupYou create an AD group similarly to creating an AD user, with the
New‐ADGroup
command as follows:
# 11. Create a new group for RK DNS Admins
$NGHT1 = @{
Name = 'RKDnsAdmins'
Path = 'OU=IT,DC=Reskit,DC=org'
Description = 'Reskit DNS Universal admins'
GroupScope = 'Universal'
}
New-ADGroup @NGHT1
This creates a new group,
RKDnsAdmins
, which is stored in the IT OU. An AD group object is similar to an AD user object in that you can move it, remove it, or update it.
AD supports two types of groups: distribution groups and security groups. A security group can be used when adjusting ACLs. A distribution group, on the other hand, cannot be used for that. Even though the distribution group object actually contains a value for the SID attribute (a little‐known fact), the AD UI prevents you from using distribution groups in ACLs.
For more details on how security groups are used in access control, see this document: docs.microsoft.com/en-us/windows/win32/ad/how-security-groups-are-used-in-access-control.
Once you create the group, you need to populate it (and view the results). To do that, you use the
Add‐ADGroupMember
and
Get‐ADGroupMember
commands, as follows:
# 12. Add a user to the DNS Admins group and view group members
Add-ADGroupMember -Identity 'RKDnsAdmins' -Members 'JerryG' | Out-Null
Get-ADGroupMember -Identity 'RKDnsAdmins'
The output, shown in Figure 3.27, shows the new user,
JerryG
, is the only member of the
RKDnsAdmins
group.
Figure 3.27: Viewing updated user details
Note that you use this group (and the
JerryG
user) in “Configuring Just Enough Administration (JEA).”
IT
TeamYou next make a further Universal group that is to contain all members of the IT team.
# 13. Make a group for the IT Team
$NGHT2 = @{
Name = 'IT Team'
Path = 'OU=IT,DC=Reskit,DC=org'
Description = 'All members of the IT Team'
GroupScope = 'Universal'
}
New-ADGroup @NGHT2
IT
Members of the
IT
Team GroupHaving created the group, you now add all members of the IT OU into this new group, with this code:
# 14. Make all Users in IT a Member Of This Group
$SB = 'OU=IT,DC=Reskit,DC=Org'
$ItUsers = Get-ADUser -Filter * -SearchBase $SB
Add-ADGroupMember -Identity 'IT Team' -Members $ItUsers
It is often useful to view the membership of a group after you update the members. The output of the following code, using
Get‐ADGroupMember
, could be useful in documenting membership of key groups:
# 15. Display Group Members of the IT Team Group
Get-ADGroupMember -Identity 'IT Team' |
Format-Table -Property SamAccountName, DistinguishedName
The output, which you can see in Figure 3.28, shows the
SamAccountName
and the
DistinguishedName
of the members of the IT Team group.
Figure 3.28: Viewing group membership
As you run the scripts in this section, it can also be handy to have the Active Directory Users and Computers MMC console open and available. This makes it easy to view the results of your scripts. It also allows you to quickly undo anything done incorrectly due, for example, to a typo in your code. As an alternative, you could also use the Active Directory Administrative Center (ADAC) for this purpose. See docs.microsoft.com/en-us/windows-server/identity/ad-ds/ad-ds-getting-started for details on the ADAC.
AD
Adding a computer to an AD is simple and similar to adding a new user or group, as follows:
# 16. Add a computer to the AD
$NCHT = @{
Name = 'Wolf'
DNSHostName = 'Wolf.Reskit.Org'
Description = 'One for Jerry'
Path = 'OU=IT,DC=Reskit,DC=Org'
OperatingSystemVersion = 'Windows Server 2019 Data Center'
}
New-ADComputer @NCHT
This code adds a single computer, called
Wolf.Reskit.Org
, into the IT OU.
For the computer to utilize the AD domain, you also need to update the computer to be a member of the domain. Adding the computer first, known as pre‐staging, requires administrative privilege, but once a computer has been added to AD, you can then join that computer to the AD without needing elevated credentials. You can find more details in this article: websistent.com/how-to-prestage-a-computer-in-active-directory/. The article is several years old, but the principles it covers remain valid.
AD
DomainThe final step in this demonstration of managing AD users, computers, and OUs is to get and output the names of the computers in your AD domain and the last time they were logged on, as follows:
# 17. See the computer accounts
Get-ADComputer -Filter * -Properties DNSHostName,LastLogonDate |
Format-Table -Property Name, DNSHostName,LastLogonDate
The output from this step, in Figure 3.29, shows the computers currently contained in the
Reskit.Org
domain. Depending on your needs, you may want to adjust the properties you display.
Figure 3.29: Viewing computers in the AD
Note that the Wolf computer you added in the previous step shows no last logon date, since no one has yet logged on to this host. Also remember that the value for the
LastLogonDate
property can be off by up to 14 days. For more information, see blogs.technet.microsoft.com/askds/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works/.
The code snippets in this section have demonstrated how you can use the PowerShell cmdlets to add/remove/update objects in the AD including computers, OUs, and users.
In “Managing AD Users, Computers, and OUs,” you saw how you can use PowerShell 7 to add/remove/update objects in the AD. In production, you may need to automate the regular changes to AD. In almost all organizations, users and computers come, change, and go. Most organizations try to automate those regular changes.
How to automate the regular changes is a popular question in many support forums, such as the PowerShell forum on Spiceworks (community.spiceworks.com/programming/powershell). A common question is how to add users to AD using a CSV file.
In many organizations, an enterprise resource planning (ERP) tool, such as SAP, might hold the master copy of user details. Any changes to that information are made within the ERP system, which can then create a CSV file containing the information to be updated in other systems (such as AD). In a smaller organization, those details might be contained in a CSV file originating from the HR department using an Excel spreadsheet.
The basic concept is that the CSV file contains a collection of users to be added (or changed/removed). When you import the CSV file into PowerShell, you create objects that represent user properties, including name, password, and so on.
There are some user properties that are always required to add a user into the AD; the password, for example. Others, such as office name or phone number, are optional. Some organizations make use of a large number of AD User attributes; others use only the bare minimum. You can easily tailor your CSV files and the scripts that process them to meet the needs of your organization.
In this example you build a simple CSV file and then use it to create users in the
Reskit.Org
domain. You run the script on the
DC1.Reskit.Org
host. This host is a domain controller you created in “Establishing a Forest Root Domain” and have used throughout this chapter.
CSV
FileThe first step in this process is to create a CSV file. This file contains the basic information about each user to be added to the AD. It looks like this:
# 1 Create CSV
$CSVDATA = @'
Firstname, Initials, Lastname, UserPrincipalName, Alias, Description, Password
S,K,Masterly, SKM, Sylvester, Data Team, Christmas42
C,B, Smith, CBS, Claire, Receptionist, Christmas42
Billy, Bob, JoeBob, BBJB, BillyBob, A Bob, Christmas42
Malcolm, Dudley, Duelittle, Malcolm, Malcolm, Mr Danger, Christmas42
'@
$CSVDATA | Out-File -FilePath C:\Foo\Users.Csv
This CSV file contains seven properties, including a plain‐text password to be assigned to the user. If you want to include more information about each user in the AD, such as office phone number, you can add additional columns to the CSV file.
CSV
With the CSV file created, the next step is to import it and, optionally, display the input data, as follows:
# 2. Import a CSV file containing the details of the users you
# want to add to AD:
$Users = Import-CSV -Path C:\Foo\Users.Csv |
Sort-Object -Property Alias
$Users | Format-Table
This creates the
$Users
array that contains the values for properties of the users to be added. You can see the output generated by this step in Figure 3.30.
Figure 3.30: Viewing users to be added to AD
When you import a CSV file, PowerShell converts the file into an array of the type
System.Management.Automation.PSCustomObject
. Each object is created with a
NoteProperty
representing each column in the CSV file. These properties are defined as being strings set to the appropriate values from the CSV file. You can view these details by piping the variable
$Users
to
Get‐Member
.
AD
To add the users to AD, you just iterate over the array of users, and for each user you create a hash table of user properties and then call
New‐ADuser
to add the user, as follows:
# 3. Add the users using the CSV
$Users |
ForEach-Object -Parallel {
$User = $_
# Create a hash table of properties to set on created user
$Prop = @{}
# Fill in values
$Prop.GivenName = $User.Firstname
$Prop.Initials = $User.Initials
$Prop.Surname = $User.Lastname
$Prop.UserPrincipalName = $User.UserPrincipalName + "@Reskit.Org"
$Prop.Displayname = $User.FirstName.Trim() + " " +
$User.LastName.Trim()
$Prop.Description = $User.Description
$Prop.Name = $User.Alias
$PW = ConvertTo-SecureString -AsPlainText $User.Password -Force
$Prop.AccountPassword = $PW
$Prop.ChangePasswordAtLogon = $true
$Prop.Path = 'OU=IT,DC=Reskit,DC=ORG'
$Prop.Enabled = $true
# Now Create the User
New-ADUser @Prop
# Finally, Display User Created
"Created $($Prop.Name)"
}
You can see the results of this step in Figure 3.31. The
New‐ADUser
command does not produce output, so the output is generated by this snippet explicitly.
Having created some additional users, you can view all the users in the
Reskit.Org
domain, as follows:
# 4. Show All Users in AD (Reskit.Org)
Get-Aduser -Filter * |
Format-Table -Property Name, UserPrincipalName
Figure 3.31: Creating new users
You can view the output from this snippet in Figure 3.32.
Figure 3.32: Viewing all users
CSV files are flexible as input to scripts that manage AD objects. You can adopt the approach shown in this section when removing users such as a user who has left the organization. Create a file of users to be removed, and your script can remove them or move them to an “Ex‐Employee” group as dictated by your policies. You can also use this technique for adding groups to the AD. Just create a CSV file of groups and then adjust this script to add the new AD groups.
This snippet uses an important new PowerShell 7 feature, which is the ‐
Parallel
parameter to
Foreach‐Object
. This snippet runs user creation in parallel. In this case, where you are adding just four users to AD on the DC, using this construct is actually just a bit slower than not using
‐Parallel
. However, this construct can be more useful were you to run this script on a client system where there were network latencies to consider, or where each iteration did more than just add a new user (such as adding the user to a group, creating files, and so on).
Managing rights and permissions can be complex even in smaller organizations. In all too many organizations, administrators of all sorts are just dropped into high‐privilege groups such as Domain Administrators or Enterprise Administrators. That can make jobs easier, but it also opens up all sorts of potential security holes.
Just Enough Administration (JEA) is a tool to enable you to implement fine‐grained administrative delegation, ensuring that a user has just enough privilege to do their job and not a bit more. JEA uses PowerShell remoting to define which users are in a specific role and what that role can do within a remoting session.
With JEA, for example, you can allow your DNS administrators to enter a remoting session on a domain controller and manage the DNS service (and very little more). Even though a user is in a remoting session on a DC, with JEA they would be unaware that other commands exist and would not be able to use them.
There are three distinct objects involved with JEA. To deploy JEA you need to develop each of these:
.PSRC
, defines a role in terms of the aliases, commands, functions, providers, and external programs an administrator can use within a JEA session. A PSRC files resembles a PowerShell module manifest..PSSC
, defines how a JEA endpoint is configured. It states the users (groups) that can use the endpoint, a JEA session, and the roles to which they have access.Once you have the two files in place, you register the JEA‐based endpoint to the server based on the session configuration.
A JEA user can enter a PowerShell remoting session or invoke commands in a remoting session specifying the constrained endpoint. PowerShell uses the user's group membership to determine the role involved and configures the PowerShell session based on the role capabilities file for that role.
In this example, you implement JEA on a domain controller,
DC1
, which you have used throughout this chapter. You created this host as a domain controller in “Establishing a Forest Root Domain.”
You also need to create or use the AD user
JerryG
. You also need to make this user a member of the
RKDnsAdmins
group. You created the user and added the account to the group in “Managing AD Users, Groups, and Computers.”
A useful feature of JEA is the use of PowerShell transcripts. With JEA, PowerShell can create a session transcript for any JEA session automatically. You first create a new folder to hold the transcripts, as follows:
# 1. Create transcripts folder
New-Item -Path C:\JEATranscripts -ItemType Directory | Out-Null
The role capabilities file defines the capabilities of a role. While you can store that file anywhere, it's good security to store it in a specific folder, as follows:
# 2. Create capabilities folder
$JEACF = "C:\JEACapabilities"
New-Item -Path $JEACF -ItemType Directory | Out-Null
You might also want to restrict access to files in this folder.
Next, you create a role capabilities file. The role capabilities file defines what a user in a role is allowed to do within a remoting session. You create a new JEA role capabilities file as follows:
# 3. Create Role Capabilities File
$RCF = Join-Path -Path $JEACF -ChildPath "RKDnsAsmins.psrc"
$RCHT = @{
Path = $RCF
Author = 'Reskit Administration'
CompanyName = 'Reskit.Org'
Description = 'Defines RKDnsAdmins role capabilities'
AliasDefinition = @{Name='gh';Value='Get-Help'}
ModulesToImport = 'Microsoft.PowerShell.Core','DnsServer'
VisibleCmdlets = ("Restart-Service",
@{ Name = "Restart-Computer";
Parameters = @{Name = "ComputerName"}
ValidateSet = 'DC1, DC2'},
'DNSSERVER\*')
VisibleExternalCommands = ('C:\Windows\System32\whoami.exe')
VisibleFunctions = 'Get-HW'
FunctionDefinitions = @{
Name = 'Get-HW'
Scriptblock = {'Hello JEA World'}}
}
New-PSRoleCapabilityFile @RCHT
The role capabilities defined in this file enable a DNS admin to do all of the following:
gh
as an alias to
Get‐Help
.DNSServer
module imported.Restart‐Service
command (but not
Get‐Service
).Restart‐Computer
, but only to restart
DC1
or
DC1
.DNSServer
module.whoami.exe
.Get‐HW
(whose definition you can see in the role capabilities file).JEA
Session Configuration FileThe JEA session configuration file is used by PowerShell to associate groups (such as the
RKDnsAdmins
group) whose members use a specific role (such as the
RKDNSAdmins
role). You can also configure some additional aspects of a JEA remoting session, as follows:
# 4. Create a JEA Session Configuration file
$SCF = 'C:\JEASessionConfiguration'
New-Item -Path $SCF -ItemType Directory | Out-Null
$P = Join-Path -Path $SCF -ChildPath 'RKDnsAdmins.pssc'
$RDHT = @{
'Reskit\RKDnsAdmins' = @{'RoleCapabilityFiles' =
'C:\JEACapabilities\RKDnsAsmins.psrc'}
}
$PSCHT= @{
Author = 'DoctorDNS@Gmail.Com'
Description = 'Session Definition for RKDnsAdmins'
SessionType = 'RestrictedRemoteServer' # ie JEA!
Path = $P # the output file
RunAsVirtualAccount = $true
TranscriptDirectory = 'C:\ JeaTranscripts'
RoleDefinitions = $RDHT # RKDnsAdmins role mapping
}
New-PSSessionConfigurationFile @PSCHT
The session configuration file's session type indicates that the remoting session is based on JEA. The configuration also tells PowerShell that the user identity in the session is based on a temporary account unique to a specific user and valid for only the duration of a JEA session.
For more details on the session configuration file, see docs.microsoft.com/powershell/scripting/learn/remoting/jea/session-configurations.
This session configuration file also sets up a
Transcripts
folder. This enables PowerShell to create a transcript of all commands within a JEA session and store that in the specified folder. These transcripts are of the same type as created by the
Start‐Transcript
command (see docs.microsoft.com/powershell/module/microsoft.powershell.host/Start-Transcript?view=powershell-7 for more details on
Start‐Transcript
). With JEA, PowerShell creates a transcript covering the entire JEA session.
It is useful to use
Test‐PSSessionConfiguration
to test the session configuration file to ensure that it is properly formatted. This command ensures that you have valid keys in the session configuration file and that values are the correct type. You can do this as follows:
# 5. Test the session configuration file
Test-PSSessionConfigurationFile -Path $P
You can see the output of this command in Figure 3.33.
Figure 3.33: Testing the session configuration file
As you can see,
Test‐PSSessionConfigurationFile
does not return a lot of information about what has been checked. However, if there are issues in the session configuration file, these are noted when you test the file.
JEA
Session EndpointNow that you have your JEA role and session configuration specified, you can create the JEA session endpoint. Before doing that, you enable PowerShell remoting explicitly using
Enable‐PSRemoting
. Then you create the JEA endpoint using
Register‐PSSessionConfiguration
, like this:
# 6. Enable Remoting and register the JEA Session Definition
Enable-PSRemoting -Force | Out-Null
$SCHT = @{
Path = $P
Name = 'RKDnsAdmins'
Force = $true
}
Register-PSSessionConfiguration @SCHT
Figure 3.34 shows the output from running this snippet.
Figure 3.34: Registering the session configuration file
This command creates a new remoting endpoint that provides a JEA environment for DNS administration.
You can use the command
Get‐PSSessionCapability
to determine what a user (in the
RKdnsAdmins
group) can do within a JEA remoting session, like this:
# 7. Check What the User Can Do
Get-PSSessionCapability -ConfigurationName RKDnsAdmins -Username 'Reskit\JerryG' |
Sort-Object -Property Module
You can see the output from these commands in Figure 3.35.
Figure 3.35: Determining session capabilities
To save space in this book, the output shows only some of the commands available in the JEA session.
JerryG
With JEA set up, you can test it by running script blocks in a JEA session; but to do that, you need a credential object. You create credentials for a user who is in the
RKDNSAdmins
group using the following commands:
# 8. Create Credentials for user JerryG
$U = 'JerryG@Reskit.Org'
$P = ConvertTo-SecureString 'Pa$$w0rd' -AsPlainText -Force
$Cred = [PSCredential]::New($U,$P)
JEA
To test that JEA is delivering what you expected, you can try to run some simple scripts inside a JEA session, as follows:
# 9. Define Three Script Blocks and an Invocation Splatting Hash Table
$SB1 = {Get-Command}
$SB2 = {Get-HW}
$SB3 = {Get-Command -Module 'DNSSERVER'}
$ICMHT = @{
ComputerName = 'DC1.Reskit.Org'
Credential = $Cred
ConfigurationName = 'RKDnsAdmins'
}
This snippet creates three script blocks:
$SB1 |
This script block gets all the commands (that is, all the commands the JEA session provides to the user). |
$SB2 |
This script block runs the
Get‐HW function. |
$SB3 |
This block shows the commands that are in the
DNSServer module and that are available to the JEA user. |
These three script blocks test the capabilities that are provided to a DNS admin within the JEA session on
DC1
.
JEA
Session?A test of JEA is to show the commands that are available within a JEA session. To achieve that, you execute the
$SB1
script block within a remoting session using the newly created JEA endpoint. You can do that as follows:
# 10. Get commands available within the JEA session
Invoke-Command -ScriptBlock $SB1 @ICMHT |
Sort-Object -Property Module |
Select-Object -First 15
This snippet gets details of all the commands available within the JEA session, sorts them by module name, and then selects the first 15 commands. You can view the output in Figure 3.36.
Figure 3.36: Checking on commands in the JEA session
You can see all the non‐DNS commands available plus the first few from the
DNSServer
module. You can see in this list the
Get‐HW
function defined only for this endpoint.
JEA
‐Defined FunctionIn the role capabilities file, you defined a function,
Get‐HW
, which is to be available within the JEA session. You can do this by invoking
$SB2
in a JEA session, as follows:
# 11. Invoke a JEA Defined Function in a JEA Session as JerryG
Invoke-Command -ScriptBlock $SB2 @ICMHT
You can see the output in Figure 3.37.
Figure 3.37: Invoking a JEA‐defined function
DNSServer
Command Available in
JEA
SessionAs a final test of JEA, you can obtain a count of how many commands are available in the JEA session and provided by the DNSServer module. You do that by invoking the
$SB3
script block, as follows:
# 12. Get DNSServer commands available to JerryG
$C = Invoke-Command -ScriptBlock $SB3 @ICMHT
"$($C.Count) DNS commands available"
You can see the output of this command in Figure 3.38.
Figure 3.38: Counting DNS server commands available
In the session configuration file, you instructed PowerShell to create PowerShell transcripts for each JEA session. You can view the contents of the transcript folder as follows:
# 13. Examine the Contents of the Transcripts Folder:
Get-ChildItem -Path $PSCHT.TranscriptDirectory
You can view the output of this snippet in Figure 3.39.
Figure 3.39: Viewing the JEA
Transcripts
folder
As you can see, there are three transcript files in the folder, one for each of the three script blocks you just ran. Because each session was short, these transcripts are also quite short. If users run long sessions, these transcript files can grow. If you are going to generate transcripts, you should ensure that older transcript files are removed.
JEA
TranscriptEach transcript in the transcript folder holds details of everything that happens within the JEA session. You can examine the transcript generated when you ran the first script block, as follows:
# 14. Examine a transcript
Get-ChildItem -Path $PSCHT.TranscriptDirectory |
Select-Object -First 1 |
Get-Content
You can see the output of this snippet in Figure 3.40.
Depending on how many transcripts you have, you may need to adjust this snippet to ensure you are looking at the correct transcript.
In the figure, you see the results of running
$SB2
in the JEA session. That script block called the
Get‐HW
function, which in turn displayed the text
Hello JEA World
. In the transcript, you can see the start and end times for the session, the user who ran the script, and details about the environment and about every command run in the session.
If the JEA endpoint is used heavily, then the size of the transcript directory can grow. As with all logging, you should have a strategy for managing the JEA transcripts.
Figure 3.40: Viewing a JEA transcript
In this chapter, you examined some of the key AD‐related actions you might need to perform using PowerShell 7. Two modules, Server Manager and the AD Deployment, both do not natively load PowerShell 7. Instead, you need to use the Windows PowerShell compatibility mechanism discussed in Chapter 2. You use
Import‐Module
to load these two modules, allowing access to the commands they contain. The Active Directory module, on the other hand, works natively in PowerShell 7.