Back to Build and harden the identity foundation

COURSE 1.1 · CONFIGURATION CHECKLIST

Build and harden the identity foundation

Secure By Design · Edition 1.0 · 20 October 2026

The addresses and names below continue the training example. Match every change to your own address plan. This network has no internet DNS dependency: names resolve on the domain controllers, and nothing is forwarded outside.

Download the workbook (Word)

Before you start

1. Design the forest, domain and OUs

About 30 min · Open this lesson

Commands:

Get-ComputerInfo | Select-Object CsName, WindowsProductName, OsBuildNumber
Get-NetIPConfiguration | Select-Object InterfaceAlias, IPv4Address, IPv4DefaultGateway
# Nothing is promoted yet: save this starting state beside the design record.

Verify: Both planned controllers are standalone Windows Server systems with static addresses on VLAN 10, and no directory exists yet. The design record names one forest, one domain, the OU plan and the Tier 0 inventory, each with its reason and source.

Evidence: Save the design record: the forest and domain decision, the OU plan, the Tier 0 inventory, and the authority behind each choice.

Notes / result / date:

2. Give every system a purpose

About 30 min · Open this lesson

Commands:

hostname
Get-NetIPConfiguration
w32tm /query /status

Verify: Server Manager shows the planned name, and the adapter's Network Connection Details show the planned static address, the default gateway 10.0.10.1 and, on SBD-DC02, DNS server 10.0.10.10. The newest Time-Service event in the System log names the time source; record it, because a configured address alone does not prove time synchronization.

Evidence: Record your VM names, OS builds, addresses, segment and gateway, time source, and any differences from the sample.

Notes / result / date:

3. Create the first domain controller

About 30 min · Open this lesson

Commands:

Get-ADDomain | Select-Object DNSRoot, NetBIOSName
Get-Service NTDS,Netlogon
Get-SmbShare -Name SYSVOL,NETLOGON

Verify: The domain is sbd.test with NetBIOS name SBD. NTDS and Netlogon are running, and SYSVOL and NETLOGON shares exist. Investigate missing services or shares before moving on.

Evidence: Record the domain, promotion outcome, service checks, and the file names of your screenshots or saved output. Do not enter passwords.

Notes / result / date:

4. Give the domain a second service point

About 30 min · Open this lesson

Commands:

Get-ADDomainController -Filter * |
  Select-Object HostName, IPv4Address, IsGlobalCatalog
repadmin /replsummary
repadmin /showrepl SBD-DC02

Verify: Sites and Services lists both controllers, each NTDS Settings shows Global Catalog checked, and Replicate Now succeeds on each connection after initial convergence. The Directory Service log holds no unresolved replication errors; read any error rather than assuming the restart completed replication.

Evidence: Record both controllers, replication status and collection time, plus any failures or follow-up actions.

Notes / result / date:

5. Ask the domain to prove it works

About 30 min · Open this lesson

Commands:

dcdiag /s:SBD-DC01
dcdiag /s:SBD-DC02
repadmin /replsummary
repadmin /showrepl *
Get-ADDomainController -Filter * | Select-Object HostName, IsGlobalCatalog
netdom query fsmo
Get-SmbShare -Name SYSVOL,NETLOGON -CimSession SBD-DC02
Get-NetConnectionProfile | Select-Object InterfaceAlias, NetworkCategory
Get-NetFirewallProfile -PolicyStore ActiveStore |
  Select-Object Name, Enabled, DefaultInboundAction

Verify: The Best Practices Analyzer scan is compliant, or every finding has a recorded cause. Replicate Now succeeds in both directions, both NTDS Settings show Global Catalog checked, Operations Masters names SBD-DC01 for every role, Shared Folders on both controllers lists SYSVOL and NETLOGON, and Windows Defender Firewall shows the Domain profile active with every profile on and inbound blocked by default. If you take the command-line route, DCDiag passes or each failure has a recorded cause. Save each screen with host, time and your reading of it; a console that opened is not a pass.

Evidence: For each controller, record the DCDiag results, replication status in both directions, the roles it holds, its network profile and firewall state, the test time, and what the results establish.

Notes / result / date:

6. Leave a build someone else can understand

About 30 min · Open this lesson

Commands:

Get-ADDomain |
  Select-Object DNSRoot, PDCEmulator, RIDMaster, InfrastructureMaster
Get-ADForest |
  Select-Object RootDomain, SchemaMaster, DomainNamingMaster

Verify: The record identifies the actual role holders and links configuration decisions to dated test results. Open issues remain visible. Lesson completion records learning activity; it is not a system authorization decision.

Evidence: Summarize the final state, reference your evidence files, and list remaining work with a next action or review trigger.

Notes / result / date:

7. Map names to the network

About 35 min · Open this lesson

Commands:

Invoke-Command SBD-DC01,SBD-DC02 { Get-WindowsFeature DNS; Get-Service DNS }
repadmin /replsummary
Get-DnsClientServerAddress -AddressFamily IPv4 -CimSession SBD-DC01,SBD-DC02
# After replication succeeds both ways (adapter name varies):
Set-DnsClientServerAddress -CimSession SBD-DC01 -InterfaceAlias Ethernet `
  -ServerAddresses 10.0.10.11,127.0.0.1
Set-DnsClientServerAddress -CimSession SBD-DC02 -InterfaceAlias Ethernet `
  -ServerAddresses 10.0.10.10,127.0.0.1
Get-DnsClientServerAddress -AddressFamily IPv4 -CimSession SBD-DC01,SBD-DC02
Get-DnsServerZone -ComputerName SBD-DC01

Verify: Services shows DNS Server running on both controllers and, once Replicate Now succeeds both ways, each controller's IPv4 properties list its partner first and 127.0.0.1 second. Members list both controllers only; the AD zones, the recursion decision, and the register rows that permit DNS are recorded.

Evidence: Save the address/name worksheet, each controller's DNS client order and the reason for it, and the initial zone inventory before changing anything else.

Notes / result / date:

8. Configure forward and reverse zones

About 20 min · Open this lesson

Commands:

Get-DnsServerZone -ComputerName SBD-DC01 |
  Select-Object ZoneName, ZoneType, IsDsIntegrated, DynamicUpdate, ReplicationScope, SecureSecondaries
Get-DnsServerZone -ComputerName SBD-DC02

Verify: The intended forward and reverse zones appear on both DNS hosts after replication. The reverse zone for this /24 is 10.0.10.in-addr.arpa.

Evidence: Capture the zone type, update policy, replication scope, and transfer policy for each zone.

Notes / result / date:

9. Create and maintain service records

About 20 min · Open this lesson

Commands:

Get-DnsServerResourceRecord -ComputerName SBD-DC01 -ZoneName sbd.test
Resolve-DnsName SBD-DC01.sbd.test -Type A -Server 10.0.10.10
Resolve-DnsName 10.0.10.10 -Type PTR -Server 10.0.10.10
# The alias part 6 relies on; its target is built in part 6
Add-DnsServerResourceRecordCName -ComputerName SBD-DC01 -ZoneName sbd.test -Name pki -HostNameAlias SBD-PKI-MGMT01.sbd.test
Resolve-DnsName pki.sbd.test -Type CNAME -Server 10.0.10.11   # after replication: SBD-PKI-MGMT01.sbd.test

Verify: DNS Manager's host and pointer records agree with the address plan. Each alias names the intended host (pki.sbd.test points at an address only once part 6 builds SBD-PKI-MGMT01), and creating a DNS name alone does not configure its service.

Evidence: Keep a record table with owner, type, target, time to live, and reason for the change.

Notes / result / date:

10. Prove resolution and directory discovery

About 30 min · Open this lesson

Commands:

foreach ($server in '10.0.10.10','10.0.10.11') {
  Write-Output "DNS server: $server"
  Resolve-DnsName 'SBD-DC01.sbd.test' -Type A -Server $server
  Resolve-DnsName 'SBD-DC02.sbd.test' -Type A -Server $server
  Resolve-DnsName '_ldap._tcp.dc._msdcs.sbd.test' -Type SRV -Server $server
}
dcdiag /test:DNS /e /v
repadmin /replsummary
# Optional, on SBD-DC02: capture the locator query
New-Item -ItemType Directory C:\Evidence -Force
pktmon filter remove
pktmon filter add DNS -p 53
pktmon start --capture --comp nics --pkt-size 0 --file-name C:\Evidence\dc-locator.etl
Resolve-DnsName '_ldap._tcp.dc._msdcs.sbd.test' -Type SRV -Server 10.0.10.10
pktmon stop
pktmon etl2txt C:\Evidence\dc-locator.etl --out C:\Evidence\dc-locator.txt
pktmon etl2pcap C:\Evidence\dc-locator.etl --out C:\Evidence\dc-locator.pcapng
pktmon filter remove

Verify: DNS Manager on both servers holds both controllers' host records and the _ldap SRV records under _msdcs, the Monitoring tab's simple query passes on each, replication is healthy, and the client still resolves internal names when either resolver is unavailable. On the command-line route, the DCDiag DNS findings are explained. If you ran the capture, it shows the query from 10.0.10.11 and the answer from 10.0.10.10.

Evidence: Keep paired lookup results with the server queried, the DCDiag and replication output, the failover test record with start, restore, and final verification times, any capture file, and a short statement of what the results do and don't establish: DNS helps users find the identity service, but a correct answer proves nothing about authentication.

Notes / result / date:

11. Find the fault and record the fix

About 15 min · Open this lesson

Commands:

Get-DnsClientServerAddress -AddressFamily IPv4
Get-DnsClientCache
Resolve-DnsName SBD-DC02.sbd.test -Server 10.0.10.10
Resolve-DnsName SBD-DC02.sbd.test -Server 10.0.10.11

Verify: The original failure is reproducibly resolved, both resolvers agree, and the change record explains the cause rather than only showing a passing screenshot.

Evidence: Complete the DNS checklist and retain the before/after lookup results with the configuration owner.

Notes / result / date:

12. Plan the domain's time

About 35 min · Open this lesson

Commands:

Get-ADForest | Select-Object RootDomain, Domains       # sbd.test: the forest root and its only domain
Get-ADDomain | Select-Object PDCEmulator               # expected: SBD-DC01.sbd.test
# The domain's Kerberos tolerance, from the Default Domain Policy (expected: MaxClockSkew = 5, in minutes)
Select-String -Pattern MaxClockSkew -Path "\\sbd.test\SYSVOL\sbd.test\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf"
# Each controller's inbound UDP 123 rule, and where each takes its time today
Invoke-Command SBD-DC01,SBD-DC02 {
  Get-NetFirewallPortFilter -PolicyStore ActiveStore | Where-Object { $_.Protocol -eq 'UDP' -and $_.LocalPort -contains '123' } |
    Get-NetFirewallRule | Select-Object DisplayName, Direction, Enabled, Action
  w32tm /query /source }

Verify: The plan names the forest root and its PDC emulator, the hierarchy each system follows, the approved reference with who checks it and when, the verification each system must show, the fallback, the Kerberos tolerance read from the Default Domain Policy, and rule F09 with each controller's inbound UDP 123 rule.

Evidence: Keep the time plan with the output behind it: the forest root and PDC emulator, the reference and checking procedure, the fallback, MaxClockSkew, rule F09, both controllers' UDP 123 firewall rules, and each controller's time source before you change anything.

Notes / result / date:

13. Make SBD-DC01 the domain's time source

About 30 min · Open this lesson

Commands:

Get-ADDomain | Select-Object PDCEmulator   # expected: SBD-DC01.sbd.test
# Hyper-V example (other hypervisors have an equivalent setting). On SBD-HV01, and the same for SBD-DC02 on SBD-HV02:
Disable-VMIntegrationService -VMName SBD-DC01 -Name 'Time Synchronization'
Get-VMIntegrationService -VMName SBD-DC01 -Name 'Time Synchronization'   # Enabled: False
# On SBD-DC01 (the PDC emulator): the reliable source, serving its own clock
w32tm /config /reliable:yes /update
Restart-Service w32time
w32tm /query /configuration   # AnnounceFlags: 5 (Local); NtpClient Type: NT5DS (Local)
w32tm /query /source          # Local CMOS Clock
w32tm /query /status          # Stratum: 1, ReferenceId: LOCL, Root Dispersion: 10s
nltest /dsgetdc:sbd.test /gtimeserv   # DC: \\SBD-DC01.sbd.test; Flags include GTIMESERV
Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-Time-Service'; Id=12} -MaxEvents 1 |
  Format-List TimeCreated, Message
# Reference check: run this as you read the approved reference, and log both readings
Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff K'
# A correction is made only by the measured difference (example: SBD-DC01 two seconds slow):
# Set-Date -Adjust ([TimeSpan]::FromSeconds(2))

Verify: The hypervisor console shows host time synchronization off for both controller VMs, and SBD-DC01's System log shows Time-Service event 12 as expected with no warning that the system lacks a time source. w32tm, the supported route for the time service's own settings, reports AnnounceFlags 5 and NT5DS, source Local CMOS Clock, stratum 1 and reference LOCL, and nltest returns SBD-DC01 with the GTIMESERV flag. The first reference check is logged with both readings and any correction.

Evidence: Record the hypervisor setting for both controller VMs; SBD-DC01's configuration, source and status output; the nltest result; the event 12 text; and the first reference check — both readings, the difference, any correction with its 4616 event, who and when.

Notes / result / date:

14. Bring every system onto the hierarchy

About 35 min · Open this lesson

Commands:

# On SBD-DC02, and on a Windows test client or member server if you have one:
w32tm /query /configuration | Select-String 'AnnounceFlags|Type:'   # SBD-DC02: AnnounceFlags: 10 (Local); Type: NT5DS (Local)
w32tm /query /configuration | Select-String '\(Policy\)'           # expected: no output
w32tm /query /source                                                # a controller, never a hypervisor provider
# A system once set by hand returns to the hierarchy:
#   w32tm /config /syncfromflags:domhier /update; Restart-Service w32time; w32tm /resync /rediscover
# The Linux chrony file, filed now (/etc/chrony.conf on the Red Hat Enterprise Linux family):
#   server SBD-DC01.sbd.test iburst
#   server SBD-DC02.sbd.test iburst
#   maxdistance 16
# ...with no pool, other server, sourcedir or allow lines; then:
#   sudo systemctl enable chronyd; sudo systemctl restart chronyd
# Each Linux build later checks:
#   chronyc sources -v     # ^* one controller, ^+ or ^- the other (^? = unusable)
#   chronyc ntpdata | grep -E '^(Remote address|Stratum|Root dispersion|Authenticated)'
#   chronyc tracking       # Reference ID: a controller; System time small and stable

Verify: Resultant Set of Policy shows no Windows Time Service setting from Group Policy, and each Windows system's newest Time-Service event names a domain controller: SBD-DC02 names SBD-DC01. w32tm on SBD-DC02 reports AnnounceFlags 10 and NT5DS. The chrony file (both controllers, maxdistance 16 and the reason for it) is filed for courses 1.2, 1.5 and 1.11 to apply.

Evidence: Keep SBD-DC02's and the member's configuration and source output, the empty (Policy) search, the rule that no VM takes its time from its host, and the filed chrony configuration with the reason for maxdistance 16 and the systems that will use it.

Notes / result / date:

15. Prove the time chain

About 30 min · Open this lesson

Commands:

New-Item -ItemType Directory C:\Evidence\time -Force | Out-Null
$f = "C:\Evidence\time\$env:COMPUTERNAME-$(Get-Date -Format yyyyMMdd)"
# On SBD-DC01, SBD-DC02 and a member:
w32tm /query /source | Out-File "$f-source.txt"
w32tm /query /status | Out-File "$f-status.txt"
w32tm /tz | Out-File "$f-tz.txt"
# On a controller: each controller's NTP offset from the PDC emulator
w32tm /monitor /domain:sbd.test | Out-File "$f-monitor.txt"
# On the member: five timed samples against SBD-DC01
w32tm /stripchart /computer:SBD-DC01.sbd.test /samples:5 /dataonly | Out-File "$f-stripchart.txt"
# The Time-Service events to keep, and every clock change (4616)
Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-Time-Service'; Id=12,35,36,37,129,142} -MaxEvents 50 |
  Select-Object TimeCreated, Id, Message | Export-Csv "$f-time-events.csv" -NoTypeInformation
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4616} -MaxEvents 20 |
  Select-Object TimeCreated, Message | Export-Csv "$f-4616.csv" -NoTypeInformation

Verify: Each system's newest Time-Service event names the expected source, and the saved System log view holds the designed events with every warning explained. w32tm /monitor shows a small offset between SBD-DC02 and the PDC emulator, the member's five samples against SBD-DC01 are small and stable, and the reference check log is kept with them.

Evidence: Keep the dated source, status, time zone, monitor and stripchart files from C:\Evidence\time, the Time-Service and 4616 extracts, the reference check log, and a one-page summary of each system's source beside the offsets measured for it.

Notes / result / date:

16. Find the time fault and record the fix

About 30 min · Open this lesson

Commands:

# Measure before changing anything (on the failing system)
w32tm /stripchart /computer:SBD-DC01.sbd.test /samples:3 /dataonly
w32tm /query /source
w32tm /query /configuration | Select-String 'Type:|NtpServer:|\(Policy\)'
Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-Time-Service'; Id=36,129} -MaxEvents 10 |
  Format-Table TimeCreated, Id -AutoSize
# Practice fault, then the fix (test client or SBD-DC02)
w32tm /config /manualpeerlist:SBD-DC01.sbd.test,0x8 /syncfromflags:manual /update
w32tm /query /source            # SBD-DC01.sbd.test,0x8: a manual peer, off the hierarchy
w32tm /config /syncfromflags:domhier /update
Restart-Service w32time
w32tm /resync /rediscover
w32tm /query /source            # a controller again
# If the PDC emulator role moves to SBD-DC02:
#   SBD-DC02: w32tm /config /reliable:yes /update; Restart-Service w32time
#   SBD-DC01: w32tm /config /syncfromflags:domhier /reliable:no /update; Restart-Service w32time
nltest /dsgetdc:sbd.test /gtimeserv   # must name the current PDC emulator
# Linux: sudo chronyc makestep, then chronyc tracking

Verify: The practice fault was found from the system's configuration before anything changed. After the fix, the System log shows Time-Service event 35 or 37 naming a controller again, with events 36 and 129 gone; w32tm reports Type NT5DS and a small, stable offset, and nltest names the PDC emulator as the reliable source. The record explains the cause rather than only showing a passing result.

Evidence: Keep the fault record: the symptom and start time, the first offset, the source and configuration output before and after, the events, the change and its rollback, and the final offsets; after a role move, the updated time plan.

Notes / result / date:

17. Understand STIGs, SCAP and checklists

About 35 min · Open this lesson

Commands:

Get-CimInstance Win32_OperatingSystem | Select-Object CSName, Caption, Version
(Get-CimInstance Win32_ComputerSystem).DomainRole   # 5 = primary domain controller (holds the PDC emulator role), 4 = backup domain controller
Get-WindowsFeature AD-Domain-Services, DNS | Select-Object Name, InstallState
Get-ADForest | Select-Object Name, ForestMode, Domains

Verify: The applicability record lists each controller's operating system and roles, the STIGs that apply with the release you will measure against, and the later-scope STIGs with an owner and a review date.

Evidence: Keep the applicability record: host, operating system, roles, each applicable STIG with its release, the later-scope STIGs with owner and date, and the command output behind each line.

Notes / result / date:

18. Stage the DISA content across the air gap

About 20 min · Open this lesson

Commands:

Get-ChildItem C:\Staging\DISA -File | Select-Object Name, Length, LastWriteTime
New-Item -ItemType Directory C:\Evidence\baseline\transfer -Force | Out-Null
Get-ChildItem C:\Staging\DISA -File | Get-FileHash -Algorithm SHA256 | Export-Csv C:\Evidence\baseline\transfer\lab-side-hashes.csv -NoTypeInformation
# connected-side-hashes.csv came with the media; no output means every hash matches
Compare-Object (Import-Csv C:\Evidence\baseline\transfer\connected-side-hashes.csv).Hash (Import-Csv C:\Evidence\baseline\transfer\lab-side-hashes.csv).Hash

Verify: Every transferred file's hash matches the value recorded on the connected side on both controllers, SCC and STIG Viewer match DISA's published checksums, SCC runs on both controllers and STIG Viewer on SBD-DC01, and every version is written down.

Evidence: Keep the transfer record in C:\Evidence\baseline\transfer on SBD-DC01: file names, versions and releases, both hash lists, the comparison result on each controller, media custody, and the date.

Notes / result / date:

19. Capture the pre-hardening baseline

About 25 min · Open this lesson

Commands:

# On each controller, after its SCC scan. Adapt the path to SCC's results directory.
New-Item -ItemType Directory C:\Evidence\baseline -Force | Out-Null
$session = Get-ChildItem "$env:USERPROFILE\SCC\Sessions" -Directory | Sort-Object Name | Select-Object -Last 1
Copy-Item $session.FullName -Destination "C:\Evidence\baseline\$env:COMPUTERNAME" -Recurse
Get-ChildItem "C:\Evidence\baseline\$env:COMPUTERNAME" -Recurse -Filter '*XCCDF-Results*.xml' | Select-Object Name, LastWriteTime
Get-ChildItem "C:\Evidence\baseline\$env:COMPUTERNAME" -Recurse -File | Get-FileHash -Algorithm SHA256 | Export-Csv "C:\Evidence\baseline\$env:COMPUTERNAME-hashes.csv" -NoTypeInformation
# On SBD-DC02: carry its set to SBD-DC01. Then, on SBD-DC01, no output means the copy is identical.
Copy-Item C:\Evidence\baseline\SBD-DC02, C:\Evidence\baseline\SBD-DC02-hashes.csv -Destination '\\SBD-DC01\C$\Evidence\baseline\' -Recurse
Compare-Object (Import-Csv C:\Evidence\baseline\SBD-DC02-hashes.csv).Hash (Get-ChildItem C:\Evidence\baseline\SBD-DC02 -Recurse -File | Get-FileHash -Algorithm SHA256).Hash

Verify: Both controllers have a dated baseline session from the same SCC version, benchmark release and profile; the already-compliant operating system defaults are noted; and both raw result sets sit hashed in C:\Evidence\baseline on SBD-DC01, with SBD-DC02's hashes matching after the copy.

Evidence: Keep the per-host result sets and their hash lists in C:\Evidence\baseline, the SCC version, benchmark release and profile, and a short note of what the operating system defaults already covered.

Notes / result / date:

20. Build the checklists and answer the manual checks

About 40 min · Open this lesson

Commands:

foreach ($t in 'SBD-DC01', 'SBD-DC02', 'sbd.test') {
  $ckl = Get-Content "C:\Evidence\baseline\checklists\$t.cklb" -Raw -Encoding UTF8 | ConvertFrom-Json
  $ckl.stigs | Select-Object @{n='Checklist';e={$t}}, stig_name, version, release_info | Format-Table -AutoSize
  $ckl.stigs.rules | Group-Object status | Select-Object @{n='Checklist';e={$t}}, Name, Count | Format-Table -AutoSize   # expect no not_reviewed
}
Get-ChildItem C:\Evidence\baseline\checklists -Filter *.cklb | Get-FileHash -Algorithm SHA256 | Export-Csv C:\Evidence\baseline\checklists-hashes.csv -NoTypeInformation

Verify: All three checklists hold the intended STIG releases and target data, each import matched by exact revision or had its mismatches reviewed, no rule is left Not Reviewed, and every manual answer carries its observation in Finding Details.

Evidence: Keep the three .cklb files with their hashes, each import's summary counts, the status counts per checklist, and any severity override with its reason.

Notes / result / date:

21. Write the findings record for hardening

About 30 min · Open this lesson

Commands:

New-Item -ItemType Directory C:\Evidence\baseline\findings -Force | Out-Null
$rows = foreach ($t in 'SBD-DC01', 'SBD-DC02', 'sbd.test') {
  $ckl = Get-Content "C:\Evidence\baseline\checklists\$t.cklb" -Raw -Encoding UTF8 | ConvertFrom-Json
  $ckl.stigs.rules | Where-Object status -eq 'open' | Select-Object @{n='Target';e={$t}}, @{n='GroupId';e={$_.group_id}}, @{n='StigId';e={$_.rule_version}}, @{n='Severity';e={$_.severity}}, @{n='CCIs';e={$_.ccis -join ' '}}, @{n='Title';e={$_.rule_title}}, Classification, Owner, TargetDate
}
$rows | Group-Object Target, Severity | Select-Object Name, Count
$rows | Export-Csv C:\Evidence\baseline\findings\findings-record.csv -NoTypeInformation
# After you fill in Classification, Owner and TargetDate and write handoff.txt, close the baseline:
Get-ChildItem C:\Evidence\baseline -Recurse -File | Get-FileHash -Algorithm SHA256 | Export-Csv C:\Evidence\baseline-manifest.csv -NoTypeInformation
Get-FileHash -Algorithm SHA256 C:\Evidence\baseline-manifest.csv

Verify: The findings record lists every open rule from the three checklists with target, severity and CCIs; each carries one of the three classifications and an owner, POA&M candidates also a planned date; the counts by severity are recorded; and handoff.txt and the manifest give part 5 every version, path and hash it reuses.

Evidence: Keep findings-record.csv, the counts by severity per checklist, the CCI-to-control notes for the open items, handoff.txt, and C:\Evidence\baseline-manifest.csv with its own hash.

Notes / result / date:

22. Design and build the OU structure

About 30 min · Open this lesson

Commands:

New-ADOrganizationalUnit -Name SBD -Path 'DC=sbd,DC=test' -ProtectedFromAccidentalDeletion $true
'Servers', 'Workstations', 'Users', 'Groups', 'Admin' | ForEach-Object { New-ADOrganizationalUnit -Name $_ -Path 'OU=SBD,DC=sbd,DC=test' -ProtectedFromAccidentalDeletion $true }
redircmp 'OU=Workstations,OU=SBD,DC=sbd,DC=test'
redirusr 'OU=Users,OU=SBD,DC=sbd,DC=test'
New-Item -ItemType Directory C:\Evidence\hardened -Force | Out-Null
Get-ADOrganizationalUnit -Filter * -Properties ProtectedFromAccidentalDeletion | Select-Object Name, DistinguishedName, ProtectedFromAccidentalDeletion | Export-Csv C:\Evidence\hardened\ou-design.csv -NoTypeInformation
Get-ADDomain | Select-Object ComputersContainer, UsersContainer
Get-ADDomainController -Filter * | Select-Object Name, ComputerObjectDN

Verify: The planned OUs exist with deletion protection, the computer and user redirects point at the Workstations and Users OUs, both controllers are still in the Domain Controllers OU, and each OU has a recorded purpose and administrator.

Evidence: Keep the OU design sheet: each OU's distinguished name, purpose and delegated administrator, the ou-design.csv export in C:\Evidence\hardened, and the redirect commands with their output.

Notes / result / date:

23. Learn how Group Policy applies

About 30 min · Open this lesson

Commands:

# Once, on SBD-DC01: create the central store from the controller's own templates
Copy-Item C:\Windows\PolicyDefinitions -Destination \\sbd.test\SYSVOL\sbd.test\Policies\PolicyDefinitions -Recurse
# On SBD-DC01: links in precedence order, and each GPO's AD and SYSVOL versions
Get-GPInheritance -Target 'DC=sbd,DC=test'
Get-GPInheritance -Target 'OU=Domain Controllers,DC=sbd,DC=test'
foreach ($dc in 'SBD-DC01', 'SBD-DC02') { Get-GPO -All -Server $dc | Select-Object @{n='DC';e={$dc}}, DisplayName, GpoStatus, @{n='AD';e={$_.Computer.DSVersion}}, @{n='SYSVOL';e={$_.Computer.SysvolVersion}} }
# On each controller's console; carry SBD-DC02's report to the same folder on SBD-DC01
New-Item -ItemType Directory C:\Evidence\hardened\policy-before -Force | Out-Null
gpresult /r /scope:computer
gpresult /h "C:\Evidence\hardened\policy-before\$env:COMPUTERNAME-gpresult.html" /f

Verify: Group Policy Management lists both controllers' GPOs in precedence order on Group Policy Inheritance and you can say why each applies; nothing is Enforced or blocked; the domain's Status tab shows every GPO's Active Directory and SYSVOL versions in agreement; settings reports read "retrieved from the central store" with SBD-DC01's templates; and each controller's starting Group Policy Results report is saved.

Evidence: Keep both starting gpresult reports in C:\Evidence\hardened\policy-before, the inheritance output for the domain and the Domain Controllers OU, the GPO version listing, and a note of the central store's source and date.

Notes / result / date:

24. Import and link the DISA GPOs

About 40 min · Open this lesson

Commands:

# On SBD-DC01. True means the package matches the hash recorded on the connected side.
$package = Get-ChildItem C:\Staging\DISA -Filter '*GPO*.zip' | Select-Object -First 1
(Get-FileHash $package.FullName -Algorithm SHA256).Hash -in (Import-Csv C:\Evidence\baseline\transfer\connected-side-hashes.csv).Hash
Expand-Archive $package.FullName -DestinationPath C:\Staging\GPO
# Per policy; take the exact names and folders from the package:
Import-GPO -BackupGpoName '<DISA GPO name>' -TargetName 'SBD <DISA GPO name>' -Path '<package GPOs folder>' -MigrationTable '<migration table>' -CreateIfNeeded
New-GPLink -Name 'SBD <DISA domain controller GPO name>' -Target 'OU=Domain Controllers,DC=sbd,DC=test' -Order 1
Get-GPInheritance -Target 'OU=Domain Controllers,DC=sbd,DC=test'
# On each controller's console; carry SBD-DC02's report to SBD-DC01:
gpupdate /force
gpresult /r /scope:computer
New-Item -ItemType Directory C:\Evidence\hardened\import -Force | Out-Null
gpresult /h "C:\Evidence\hardened\import\$env:COMPUTERNAME-gpresult.html" /f
w32tm /query /source   # SBD-DC01: Local CMOS Clock; SBD-DC02: a domain controller
w32tm /query /configuration | Select-String '\(Policy\)'   # expected: no output, so no time setting comes from Group Policy
repadmin /replsummary

Verify: The package matched its connected-side hash, settings reports read from the central store, placeholder groups map to real ones, and Group Policy Management shows the links following the OU design with the domain controller policy at link order 1 and nothing Enforced or blocked. Group Policy Results for both controllers lists the SBD DISA GPOs as applied, time and replication are unchanged with no time setting from Group Policy, and every deviation has a written reason and owner.

Evidence: Keep the import record — package release and hash check, each GPO's name and source, the migration table, the links with their order, the loopback decision, and both gpresult reports in C:\Evidence\hardened\import — and the deviation list with rationale and owner.

Notes / result / date:

25. Rescan, reconcile, and record

About 35 min · Open this lesson

Commands:

# On SBD-DC01: the manifest's hash must match part 4's record; no Compare-Object output means no baseline file changed
Get-FileHash -Algorithm SHA256 C:\Evidence\baseline-manifest.csv
Compare-Object (Import-Csv C:\Evidence\baseline-manifest.csv).Hash (Get-ChildItem C:\Evidence\baseline -Recurse -File | Get-FileHash -Algorithm SHA256).Hash
Get-Content C:\Evidence\baseline\findings\handoff.txt   # the SCC version, benchmark release and profile to reuse
# On each controller, after its SCC rescan (adapt the path to SCC's results directory); SBD-DC02 then copies its set to SBD-DC01
$session = Get-ChildItem "$env:USERPROFILE\SCC\Sessions" -Directory | Sort-Object Name | Select-Object -Last 1
Copy-Item $session.FullName -Destination "C:\Evidence\hardened\$env:COMPUTERNAME" -Recurse
Get-ChildItem "C:\Evidence\hardened\$env:COMPUTERNAME" -Recurse -File | Get-FileHash -Algorithm SHA256 | Export-Csv "C:\Evidence\hardened\$env:COMPUTERNAME-hashes.csv" -NoTypeInformation
Copy-Item C:\Evidence\hardened\SBD-DC02, C:\Evidence\hardened\SBD-DC02-hashes.csv -Destination '\\SBD-DC01\C$\Evidence\hardened\' -Recurse   # on SBD-DC02
# On SBD-DC01: verify the copy, then list each changed rule twice, <= its baseline result and => its result now
Compare-Object (Import-Csv C:\Evidence\hardened\SBD-DC02-hashes.csv).Hash (Get-ChildItem C:\Evidence\hardened\SBD-DC02 -Recurse -File | Get-FileHash -Algorithm SHA256).Hash
function Read-Xccdf($dir) { Get-ChildItem $dir -Recurse -Filter '*XCCDF-Results*.xml' | Select-Xml -XPath "//*[local-name()='rule-result']" | ForEach-Object { [pscustomobject]@{ Rule = $_.Node.idref; Result = $_.Node.result } } }
foreach ($h in 'SBD-DC01', 'SBD-DC02') { Compare-Object (Read-Xccdf "C:\Evidence\baseline\$h") (Read-Xccdf "C:\Evidence\hardened\$h") -Property Rule, Result | Sort-Object Rule | Export-Csv "C:\Evidence\hardened\$h-changes.csv" -NoTypeInformation }
New-Item -ItemType Directory C:\Evidence\hardened\checklists, C:\Evidence\hardened\findings -Force | Out-Null
Copy-Item C:\Evidence\baseline\checklists\*.cklb C:\Evidence\hardened\checklists
Import-Csv C:\Evidence\baseline\findings\findings-record.csv | Select-Object *, Resolution, Evidence | Export-Csv C:\Evidence\hardened\findings\findings-record.csv -NoTypeInformation
Get-ChildItem C:\Evidence\hardened\checklists -Filter *.cklb | Get-FileHash -Algorithm SHA256 | Export-Csv C:\Evidence\hardened\checklists-hashes.csv -NoTypeInformation   # after the STIG Viewer updates

Verify: The manifest hash matched and no baseline file changed, both rescans used the SCC version, benchmark release and profile in handoff.txt, each controller's changes are listed and attributed, the checklist copies carry the new results and re-checked answers while the baseline copies keep their hashes, and every findings-record item has a resolution.

Evidence: Keep both rescan sessions with their hash lists, the two change lists, the updated checklist copies with their hashes, and the findings record with a resolution and evidence for every item, all in C:\Evidence\hardened.

Notes / result / date:

26. Set the rescan cadence and exception record

About 25 min · Open this lesson

Commands:

# Seed the exception record from the findings resolved as deviations; add lesson 24's deviations and fill in every column
Import-Csv C:\Evidence\hardened\findings\findings-record.csv | Where-Object Resolution -eq 'deviation' | Select-Object Target, GroupId, StigId, Title, Setting, Reason, CompensatingMeasure, RiskNote, Owner, ReviewDate | Export-Csv C:\Evidence\exception-record.csv -NoTypeInformation -NoClobber
Copy-Item C:\Evidence\exception-record.csv "C:\Evidence\hardened\exception-record-$(Get-Date -Format yyyy-MM-dd).csv"   # once it is complete
# The applied state as records; course 1.12 schedules the GPO backup itself
Get-GPOReport -All -ReportType Html -Path C:\Evidence\hardened\gpo-report.html
'DC=sbd,DC=test', 'OU=Domain Controllers,DC=sbd,DC=test', 'OU=Servers,OU=SBD,DC=sbd,DC=test' | ForEach-Object { (Get-GPInheritance -Target $_).GpoLinks } | Select-Object Target, Order, DisplayName, Enabled, Enforced | Export-Csv C:\Evidence\hardened\gpo-links.csv -NoTypeInformation
Get-ChildItem C:\Evidence\hardened -Recurse -File | Get-FileHash -Algorithm SHA256 | Export-Csv C:\Evidence\hardened-manifest.csv -NoTypeInformation
Get-FileHash -Algorithm SHA256 C:\Evidence\hardened-manifest.csv

Verify: The exception record has owners, compensating measures and review dates, with a dated copy filed, the GPO reports and link export are filed, the evidence folder is sealed in the manifest with its own hash recorded, and the rescan triggers are written where part 6 will meet them.

Evidence: Finish the hardening checklist: the living exception record in C:\Evidence and its dated copy, the GPO report and link export, C:\Evidence\hardened-manifest.csv with its own hash, the cadence, and the named owner of the next review.

Notes / result / date:

27. Design the two-tier trust hierarchy

About 35 min · Open this lesson

Commands:

# Run on the future issuing CA, not the offline root.
Get-CimInstance Win32_ComputerSystem | Select-Object Name, Domain, PartOfDomain
Resolve-DnsName SBD-DC01.sbd.test
Resolve-DnsName SBD-DC02.sbd.test

Verify: The written plan separates trust, issuance, publication, and administration. It identifies who renews CRLs before they expire, and every key size, hash, lifetime, and CRL period has a recorded value and the setting that applies it.

Evidence: Keep the approved hierarchy, the cryptography and lifetime table with the setting that applies each value, and a worksheet mapping every certificate/CRL URL to its actual publisher and file.

Notes / result / date:

28. Configure the offline Root CA

About 30 min · Open this lesson

Commands:

# Inspect locally on SBD-ROOTCA01 during the approved root session.
Get-CimInstance Win32_ComputerSystem | Select-Object Name, Domain, PartOfDomain
Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff K'   # read against the approved reference; log both readings
Get-Service CertSvc
certutil -getreg CA\CRLPublicationURLs
certutil -getreg CA\CACertPublicationURLs
certutil -getreg CA\ValidityPeriodUnits
certutil -getreg CA\CRLPeriodUnits

Verify: The host remains disconnected, its clock matches the approved reference, the CA is standalone/root, requests require approval, and the planned subordinate URLs and 10-year subordinate validity are configured before signing.

Evidence: Record the root certificate identity, policy file, publication settings, CRL dates, backup location, the clock check, and operator sign-off. Protect backups containing private keys.

Notes / result / date:

29. Create the issuing CA request

About 25 min · Open this lesson

Commands:

w32tm /query /source   # a domain controller, never Local CMOS Clock or a hypervisor provider
# Use the actual request path chosen in the wizard.
certutil -dump C:\CAConfig\IssuingCA.req
Get-FileHash -Algorithm SHA256 C:\CAConfig\IssuingCA.req

Verify: The request matches the approved issuing identity and key profile. The CA is waiting for its signed certificate; do not troubleshoot that expected state by installing another CA.

Evidence: Retain the request, its reviewed fields and hash, and the request-transfer record. Do not place private keys in the transfer package.

Notes / result / date:

30. Sign through the offline ceremony

About 30 min · Open this lesson

Commands:

# On SBD-ROOTCA01, first: read this against the approved reference and log both readings
Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff K'
# Inspect public artifacts; use your actual approved paths.
certutil -dump C:\CAConfig\IssuingCA.cer
certutil -dump C:\CAConfig\RootCA.crl
Get-FileHash -Algorithm SHA256 C:\CAConfig\IssuingCA.cer, C:\CAConfig\RootCA.cer, C:\CAConfig\RootCA.crl

Verify: The root's clock matched the approved reference, and artifact identities, signatures, validity, and custody checks pass. The root remains offline; a field dump alone is not end-to-end trust validation.

Evidence: Keep the clock check, approval, request ID, artifact hashes, verified certificate/CRL properties, media custody, and root shutdown evidence together.

Notes / result / date:

31. Activate the issuing CA and distribute trust

About 25 min · Open this lesson

Commands:

# Before installing the signed certificate: the ceremony's root certificate and CRL, into the local stores
certutil -addstore -f Root C:\CAConfig\RootCA.cer
certutil -addstore -f Root C:\CAConfig\RootCA.crl
# Inspect the issuing CA after installation and publication.
Get-Service CertSvc
certutil -getreg CA\CRLPublicationURLs
certutil -getreg CA\CACertPublicationURLs
certutil -store Root
certutil -store CA

Verify: Certificate Services is running, the AIA and CDP settings carry the planned pki.sbd.test URLs, and the approved root reaches domain clients through policy.

Evidence: Keep the CA installation record, the AIA and CDP settings, and the trust-distribution policy.

Notes / result / date:

32. Build the pki.sbd.test publication site

About 30 min · Open this lesson

Commands:

# On SBD-DC01 (VLAN 10): the name and the path from outside VLAN 30
Resolve-DnsName pki.sbd.test
Test-NetConnection pki.sbd.test -Port 80
# On SBD-PKI-MGMT01: the site, and double escaping allowed there for delta CRL names
Get-Website | Select-Object Name, State, PhysicalPath
Get-WebConfigurationProperty -PSPath 'IIS:\Sites\<PKI site>' -Filter system.webServer/security/requestFiltering -Name allowDoubleEscaping

Verify: pki.sbd.test resolves to the publication host from outside VLAN 30, the site answers on port 80 with double escaping allowed only there, and the publication folder's permissions separate writers from readers.

Evidence: Record the site configuration, folder permissions, the DNS resolution output from SBD-DC01, and rule F18, the register row that permits the path.

Notes / result / date:

33. Publish the trust data at its advertised URLs

About 25 min · Open this lesson

Commands:

# On SBD-DC01 (VLAN 10), with nothing cached; IssuingCA.cer is the ceremony's public certificate
certutil -urlcache * delete
certutil -verify -urlfetch C:\Evidence\pki\IssuingCA.cer
'<issuing CA CRL name>.crl', '<issuing CA CRL name>+.crl' | ForEach-Object { (Invoke-WebRequest "http://pki.sbd.test/$_" -UseBasicParsing).StatusCode }   # 200 for the base and the delta CRL
# On SBD-PKI-MGMT01: what the site serves, with timestamps
Get-ChildItem C:\inetpub\pkiroot | Select-Object Name, LastWriteTime

Verify: Enterprise PKI shows the issuing CA's certificate and every AIA and CDP location as OK, checked from SBD-DC01 outside VLAN 30; each URL the certificate carries opens the current file in Edge; the issuing CA's base and delta CRLs answer at their URLs; and the CRL refresh lands at the same filename automatically.

Evidence: Keep the URL-to-file mapping table, the folder listing with timestamps, and the urlfetch and CRL retrieval output from SBD-DC01.

Notes / result / date:

34. Separate template administration

About 25 min · Open this lesson

Commands:

Get-ADGroup 'SBD PKI Admins' -Properties Members | Select-Object Name, Members
certutil -template | Select-String 'TemplatePropCommonName' | Select-Object -First 10

Verify: The templates console works from SBD-PKI-MGMT01, the PKI administrators group holds the delegated permissions, and the written change process names requester, reviewer, and versioning action.

Evidence: Keep the delegation record, the group membership listing, and the signed-off template change process.

Notes / result / date:

35. Publish the first working templates

About 30 min · Open this lesson

Commands:

# On SBD-PKI-MGMT01: the templates the CA publishes (use your CA's host\name configuration string)
certutil -CATemplates -config 'SBD-SUBCA01\SBD Issuing CA'
# Each SBD template's minimum key size and subject-name flags, read from the directory
Get-ADObject -SearchBase 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=sbd,DC=test' -Filter "Name -like 'SBD*'" -Properties displayName, msPKI-Minimal-Key-Size, msPKI-Certificate-Name-Flag | Select-Object Name, displayName, msPKI-Minimal-Key-Size, msPKI-Certificate-Name-Flag

Verify: The CA publishes exactly the three planned templates, each shows the approved key size and permissions, and each carries a completed change record.

Evidence: Keep each template's configuration export, its change record, and the CA's published-template listing.

Notes / result / date:

36. Validate enrollment, revocation, and renewal

About 35 min · Open this lesson

Commands:

# On SBD-DC01, SBD-DC02 and the pilot, SBD-PKI-MGMT01, once SBD Certificate Autoenrollment is linked at the domain root
gpupdate /force
certutil -pulse
Get-ChildItem Cert:\LocalMachine\My | Select-Object @{n='Template';e={ $x = $_.Extensions | Where-Object { $_.Oid.Value -eq '1.3.6.1.4.1.311.21.7' }; if ($x) { $x.Format($false) } }}, DnsNameList, NotAfter, Thumbprint
# On SBD-DC01 (outside VLAN 30): export its certificate, then verify it and the pilot's with nothing cached
New-Item -ItemType Directory C:\Evidence\pki -Force | Out-Null
$dc = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.EnhancedKeyUsageList.ObjectId -contains '1.3.6.1.5.2.3.5' } | Select-Object -First 1   # KDC Authentication
Export-Certificate -Cert $dc -FilePath C:\Evidence\pki\SBD-DC01-dc.cer
certutil -urlcache * delete
certutil -verify -urlfetch C:\Evidence\pki\SBD-DC01-dc.cer
certutil -verify -urlfetch C:\Evidence\pki\pilot.cer   # the pilot's certificate, exported on SBD-PKI-MGMT01 with Export-Certificate
# On SBD-SUBCA01: revoke only the disposable test certificate, then publish a new CRL
certutil -revoke '<test certificate serial number>' 5   # 5 = cessation of operation
certutil -crl
# Back on SBD-DC01: the test certificate must now fail as revoked
certutil -urlcache * delete
certutil -verify -urlfetch C:\Evidence\pki\test.cer
# After the scans (each carried to C:\Evidence\pki\<host name> on SBD-DC01) and the checklist updates: record the new GPOs (course 1.12 backs them up), then seal the evidence
Get-GPOReport -Name 'SBD Certificate Autoenrollment' -ReportType Html -Path C:\Evidence\pki\gpo-report-autoenrollment.html   # and any root-trust GPO from lesson 31
(Get-GPInheritance -Target 'DC=sbd,DC=test').GpoLinks | Select-Object Target, Order, DisplayName, Enabled, Enforced | Export-Csv C:\Evidence\pki\gpo-links.csv -NoTypeInformation
Get-ChildItem C:\Evidence\pki -Recurse -File | Get-FileHash -Algorithm SHA256 | Export-Csv C:\Evidence\pki-manifest.csv -NoTypeInformation
Get-FileHash -Algorithm SHA256 C:\Evidence\pki-manifest.csv

Verify: Both controllers and the pilot enroll hands-free and verify with live retrieval from pki.sbd.test; after revocation and publication the test certificate is rejected for the expected reason; SBD-SUBCA01 and SBD-PKI-MGMT01 have first baselines and both controllers a rescan; and the domain controller certificate rule is answered with its evidence, its DoD companion recorded as an exception.

Evidence: Finish the PKI checklist with each controller's and the pilot's certificate details, urlfetch and revocation results, renewal dates and owners, the items handed to course 1.12 for backup, the first baselines of SBD-SUBCA01 and SBD-PKI-MGMT01, the controllers' rescans, the updated checklist copies, the exception-record row, the GPO report and link export, and C:\Evidence\pki-manifest.csv with its own hash.

Notes / result / date:

37. Design the addressing service

About 35 min · Open this lesson

Commands:

# At SBD-DC01's console (Tier 0): the values and identities the design names
Resolve-DnsName SBD-DC01.sbd.test -Server 10.0.10.11; Resolve-DnsName SBD-DC02.sbd.test -Server 10.0.10.10
Get-ADOrganizationalUnit -Identity 'OU=Servers,OU=SBD,DC=sbd,DC=test' | Select-Object Name, DistinguishedName
Get-ADGroupMember 'Enterprise Admins' | Select-Object Name   # who can authorize a DHCP server
Get-ADGroupMember DnsUpdateProxy   # expected: no members
Get-DnsServerZone -ComputerName SBD-DC01 | Select-Object ZoneName, IsDsIntegrated, DynamicUpdate, ReplicationScope

Verify: The design records VLAN 80 as the only leased segment, the full-subnet scope with its two exclusion ranges and no reservations, the lease length with its reason, the three options, who holds each of the three identities, and the network's confirmation that the core relays VLAN 80 to 10.0.20.10 under F04.

Evidence: Keep the DHCP design (scope, exclusions, lease length, options, identities), the relay confirmation with where it came from, and the register rows that name SBD-DHCP01.

Notes / result / date:

38. Build SBD-DHCP01 and its scope

About 30 min · Open this lesson

Commands:

# At SBD-DC01's console (Tier 0): the group and its one delegation; then create each person's named
# server-administration account in the Admin OU and add it to the group (New-ADUser, Add-ADGroupMember)
New-ADGroup 'SBD Tier 1 Admins' -GroupScope Global -Path 'OU=Admin,OU=SBD,DC=sbd,DC=test'
dsacls 'OU=Servers,OU=SBD,DC=sbd,DC=test' /G 'SBD\SBD Tier 1 Admins:CC;computer'
# On SBD-DHCP01 as its local Administrator: join with a server-administration account, then, after the restart:
Add-Computer -DomainName sbd.test -OUPath 'OU=Servers,OU=SBD,DC=sbd,DC=test' -Credential (Get-Credential) -Restart
Add-LocalGroupMember Administrators -Member 'SBD\SBD Tier 1 Admins'; Remove-LocalGroupMember Administrators -Member 'SBD\Domain Admins'
# As a server-administration account: the role and its local groups
Install-WindowsFeature DHCP -IncludeManagementTools; Add-DhcpServerSecurityGroup; Restart-Service DHCPServer
# At SBD-DC01's console, as an Enterprise Admins member: authorize
Install-WindowsFeature RSAT-DHCP; Add-DhcpServerInDC -DnsName SBD-DHCP01.sbd.test -IPAddress 10.0.20.10
# Back on SBD-DHCP01: the authorization check, then the scope, inactive until it is complete
Restart-Service DHCPServer; Get-DhcpServerInDC
Get-WinEvent -FilterHashtable @{ProviderName='Microsoft-Windows-DHCP-Server'; Id=1044} -MaxEvents 1
Add-DhcpServerv4Scope -Name 'VLAN 80 workstations' -StartRange 10.0.80.1 -EndRange 10.0.80.254 -SubnetMask 255.255.255.0 -LeaseDuration 8.00:00:00 -State InActive
Add-DhcpServerv4ExclusionRange -ScopeId 10.0.80.0 -StartRange 10.0.80.1 -EndRange 10.0.80.99
Add-DhcpServerv4ExclusionRange -ScopeId 10.0.80.0 -StartRange 10.0.80.200 -EndRange 10.0.80.254
Set-DhcpServerv4OptionValue -ScopeId 10.0.80.0 -Router 10.0.80.1 -DnsServer 10.0.10.10, 10.0.10.11 -DnsDomain sbd.test
Set-DhcpServerv4DnsSetting -DynamicUpdates Never   # lesson 39 turns registration on
Set-DhcpServerv4Scope -ScopeId 10.0.80.0 -State Active
Get-DhcpServerv4Lease -ScopeId 10.0.80.0   # after SBD-TEST80 runs ipconfig /release and ipconfig /renew
w32tm /query /source   # a domain controller, as part 3 designed

Verify: The DHCP console's Manage authorized servers lists SBD-DHCP01 at 10.0.20.10, and event 1044 in the DHCP-Server operational log shows it servicing clients. The scope's Address Pool covers 10.0.80.1–254 with exclusions .1–.99 and .200–.254, the lease is 8 days, Scope Options hold the three options, and DNS updates are off. Computer Management shows SBD Tier 1 Admins as the only domain group in its local Administrators, its newest Time-Service event names a domain controller, and the test client's first lease came from 10.0.20.10 through the relay.

Evidence: Record the build sheet (OU placement, local Administrators, time source), the authorization output and event 1044, the scope, exclusion, and option configuration, and the test client's ipconfig /all with the matching lease.

Notes / result / date:

39. Register client names securely

About 30 min · Open this lesson

Commands:

# At SBD-DC01's console (Tier 0): the reverse zone (part 2's replication scope), the update account, an empty proxy group
Add-DnsServerPrimaryZone -NetworkId '10.0.80.0/24' -ReplicationScope Domain -DynamicUpdate Secure
New-ADUser svc-dhcp-dns -Path 'OU=Admin,OU=SBD,DC=sbd,DC=test' -AccountPassword (Read-Host -AsSecureString) -PasswordNeverExpires $true -Enabled $true
Get-ADGroupMember DnsUpdateProxy   # expected: no members
# On SBD-DHCP01: the credential, then registration for every client, with name protection
Set-DhcpServerDnsCredential -Credential (Get-Credential 'SBD\svc-dhcp-dns')
Set-DhcpServerv4DnsSetting -DynamicUpdates Always -UpdateDnsRRForOlderClients $true -DeleteDnsRROnLeaseExpiry $true -NameProtection $true
Get-DhcpServerDnsCredential; Get-DhcpServerv4DnsSetting
# On SBD-TEST80: renew, then ask both controllers for the name, the address, and the DHCID record
ipconfig /renew
Resolve-DnsName SBD-TEST80.sbd.test -Server 10.0.10.11
Resolve-DnsName (Resolve-DnsName SBD-TEST80.sbd.test -Type A -Server 10.0.10.10).IPAddress -Type PTR -Server 10.0.10.10
Resolve-DnsName SBD-TEST80.sbd.test -Type DHCID -Server 10.0.10.10
# At SBD-DC01's console: the record's owner (sbd.test lives in DomainDnsZones in this sample)
Import-Module ActiveDirectory
(Get-Acl 'AD:DC=SBD-TEST80,DC=sbd.test,CN=MicrosoftDNS,DC=DomainDnsZones,DC=sbd,DC=test').Owner   # SBD\svc-dhcp-dns

Verify: DNS Manager shows the 10.0.80 reverse zone with secure updates; Active Directory Users and Computers shows svc-dhcp-dns in Domain Users only and DnsUpdateProxy empty; the DHCP console's Credentials dialog names svc-dhcp-dns and its DNS tab shows Name Protection on with cleanup of records when leases are deleted; and the test client's A, PTR and DHCID records appear on both controllers, each owned by SBD\svc-dhcp-dns.

Evidence: Keep the zone and account settings, the empty DnsUpdateProxy result, the server's DNS settings and the credential's account name (never the password), the audit-log lines for the update, both controllers' answers, and the record's owner.

Notes / result / date:

40. Audit leases and write the restore note

About 30 min · Open this lesson

Commands:

Get-DhcpServerAuditLog   # Enable: True; Path: C:\Windows\system32\dhcp
$dir = Join-Path $env:windir 'System32\dhcp'
Get-Content (Join-Path $dir ('DhcpSrvLog-{0}.log' -f (Get-Date).ToString('ddd', [cultureinfo]'en-US'))) -Tail 5
# The daily task's work (SYSTEM, 00:15): yesterday's file kept under its date, with its hash
New-Item -ItemType Directory D:\DHCP\AuditArchive -Force | Out-Null
$y = (Get-Date).AddDays(-1); $copy = 'D:\DHCP\AuditArchive\DhcpSrvLog-{0:yyyy-MM-dd}.log' -f $y
Copy-Item (Join-Path $dir ('DhcpSrvLog-{0}.log' -f $y.ToString('ddd', [cultureinfo]'en-US'))) $copy
Get-FileHash $copy -Algorithm SHA256 | Export-Csv D:\DHCP\AuditArchive\catalog.csv -Append -NoTypeInformation
# Weekly review: pool use, who holds leases, who may serve
Get-DhcpServerv4ScopeStatistics -ScopeId 10.0.80.0 | Select-Object ScopeId, InUse, Free, PercentageInUse
Get-DhcpServerv4Lease -ScopeId 10.0.80.0 | Select-Object IPAddress, HostName, ClientId, AddressState, LeaseExpiryTime
Get-DhcpServerInDC   # SBD-DHCP01 alone
# For the restore note: the automatic copy sits on the same disk (BackupPath), so course 1.12 schedules the real backup
Get-DhcpServerDatabase
Get-DhcpServerDnsCredential   # the account the note says to set again after any restore (never the password)

Verify: The DHCP console shows audit logging on, File Explorer shows free space to spare on the log volume and yesterday's file in the archive under its date, with its hash in the catalog; Address Leases holds only known machines; Manage authorized servers lists SBD-DHCP01 alone; and the restore note names the credential step for course 1.12's backup.

Evidence: Keep the audit log settings, the archive catalog, the dated lease review, the authorized-server list, and the restore note for course 1.12.

Notes / result / date:

41. Prove the service and hand off

About 30 min · Open this lesson

Commands:

# On SBD-TEST80: release, renew, and read what the client believes
ipconfig /release; ipconfig /renew; ipconfig /all
# On SBD-DHCP01: the lease, then, after the MAC change, the refused takeover
Get-DhcpServerv4Lease -ScopeId 10.0.80.0 | Select-Object IPAddress, HostName, ClientId, LeaseExpiryTime
Get-WinEvent -FilterHashtable @{ProviderName='Microsoft-Windows-DHCP-Server'; Id=1340} -MaxEvents 1 | Format-List TimeCreated, Message
Resolve-DnsName SBD-TEST80.sbd.test -Server 10.0.10.10   # still the original address
Remove-DhcpServerv4Lease -IPAddress '<stray lease address>'   # after the original MAC is back
# Before installing SCC: no output means every copied file is on the connected-side list
(Get-ChildItem C:\Staging\DISA\* -File -Exclude *.csv | Get-FileHash -Algorithm SHA256).Hash | Where-Object { $_ -notin (Import-Csv C:\Staging\DISA\connected-side-hashes.csv).Hash }
# After the SCC scan: keep the session unmodified (adapt the path to SCC's results directory), then seal the folder
$session = Get-ChildItem (Join-Path $env:USERPROFILE 'SCC\Sessions') -Directory | Sort-Object Name | Select-Object -Last 1
New-Item -ItemType Directory C:\Evidence\dhcp\baseline -Force | Out-Null
Copy-Item $session.FullName -Destination C:\Evidence\dhcp\baseline\SBD-DHCP01 -Recurse
Get-ChildItem C:\Evidence\dhcp -Recurse -File | Get-FileHash -Algorithm SHA256 | Export-Csv C:\Evidence\dhcp-manifest.csv -NoTypeInformation
Get-FileHash -Algorithm SHA256 C:\Evidence\dhcp-manifest.csv
# Run the same two lines at SBD-DC01's console for the Tier 0 half, its own C:\Evidence\dhcp

Verify: The test client holds a lease from the pool with every planned option; its A, PTR, and DHCID records answer from both controllers and belong to SBD\svc-dhcp-dns; the takeover attempt was refused with event 1340 and left the record unchanged; SBD-DHCP01 has a first baseline with every open item classified; and both manifests and the handoff are filed.

Evidence: Keep the client's ipconfig /all, the lease and audit-log lines, the takeover attempt with event 1340, the SCC session with its hashes and classified findings, both manifests with their own hashes, and the handoff note.

Notes / result / date:

42. Design the tiers and the admin seats

About 30 min · Open this lesson

Commands:

# At SBD-DC01's console until lesson 43 builds AdminJumpBox01: OU targets, current account separation, and the role groups.
Get-ADOrganizationalUnit -Filter * | Select-Object Name, DistinguishedName
Get-ADGroupMember 'Domain Admins' | Select-Object Name, SamAccountName
'SBD Tier 0 Admins', 'SBD Tier 2 Admins', 'SBD Audit Custodians' | ForEach-Object { New-ADGroup $_ -GroupScope Global -Path 'OU=Admin,OU=SBD,DC=sbd,DC=test' }
Get-ADGroup -SearchBase 'OU=Admin,OU=SBD,DC=sbd,DC=test' -Filter * | Select-Object Name   # SBD Tier 1 Admins came from part 7

Verify: The tier map names systems, OUs, admin seats, accounts, and groups per tier, with SBD-ADMIN-PC01 a virtual machine on SBD-ADMIN-SRV01; the exception record holds the seat plan's STIG differences and open PAW items; the role groups exist in the Admin OU; and the register has the new permit and deny rows with owners.

Evidence: Keep the tier access matrix, the seat plan with its STIG rationale and recorded differences, the per-administrator account plan with its groups, and the updated register rows.

Notes / result / date:

43. Build the administration systems

About 45 min · Open this lesson

Commands:

Get-ADComputer -SearchBase 'OU=Admin,OU=SBD,DC=sbd,DC=test' -Filter * | Select-Object Name, DistinguishedName
Get-GPInheritance -Target 'OU=Admin Servers,OU=Admin,OU=SBD,DC=sbd,DC=test'
Get-GPInheritance -Target 'OU=Admin Workstations,OU=Admin,OU=SBD,DC=sbd,DC=test'
Get-LocalGroupMember Administrators   # each seat: its maintenance group, no tier group
# On SBD-ADMIN-SRV01: its one virtual machine, and what host and guest share.
Get-VM | Select-Object Name, Generation, State, AutomaticStartAction   # Start
(Get-VMFirmware SBD-ADMIN-PC01).SecureBoot; (Get-VMSecurity SBD-ADMIN-PC01).TpmEnabled
Get-VMHost | Select-Object EnableEnhancedSessionMode   # False
Get-VMIntegrationService SBD-ADMIN-PC01 | Select-Object Name, Enabled   # Guest Service Interface, Key-Value Pair Exchange, Time Synchronization: False
Get-LocalGroupMember 'Hyper-V Administrators'   # the console users' Tier 1 accounts
Resolve-DnsName SBD-ADMIN-PC01.sbd.test   # 10.0.60.30
Invoke-Command SBD-DHCP01.sbd.test { Get-DhcpServerv4Scope }   # Tier 1 account, over remote management (A02)
# Inside SBD-ADMIN-PC01: Credential Guard is running when the list includes 1.
$dg = Get-CimInstance Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard
$dg.SecurityServicesRunning
w32tm /query /source   # a domain controller, never the VM IC Time Synchronization Provider
# On AdminJumpBox01: Hyper-V stays Available, never Installed.
Get-WindowsFeature Hyper-V | Select-Object Name, InstallState
Uninstall-WindowsFeature RSAT-DHCP -ComputerName SBD-DC01   # part 7's DHCP tools leave the controller
Test-NetConnection SBD-DC01.sbd.test -Port 5985
gpresult /r /scope:computer

Verify: AdminJumpBox01 and SBD-ADMIN-SRV01 sit in Admin Servers and the SBD-ADMIN-PC01 virtual machine in Admin Workstations, with the STIG and seat GPOs applied; each seat's local Administrators holds its maintenance group, no tier group; SBD-ADMIN-SRV01 runs only SBD-ADMIN-PC01 (10.0.60.30, generation 2, Secure Boot on, TPM, Credential Guard, starts with the host) with enhanced session mode, the Guest Service Interface, Key-Value Pair Exchange, and Time Synchronization off, and the guest takes its time from a domain controller; AdminJumpBox01 has no Hyper-V role; SBD-DHCP01 is managed from SBD-ADMIN-SRV01 and the DHCP tools are gone from SBD-DC01; management ports answer from the correct seat per tier; and user space can open no session toward VLAN 60.

Evidence: Record each seat's build sheet (for the virtual machine: host, generation, address), OU placement, GPO links, gpresult output, local group members, host and guest settings with the guest's time source, the administration moves, and register rows.

Notes / result / date:

44. Restrict where privilege can sign in

About 45 min · Open this lesson

Commands:

Get-ADComputer -SearchBase 'OU=Tier 0 Servers,OU=SBD,DC=sbd,DC=test' -Filter * | Select-Object Name
Get-ADGroupMember 'Protected Users' | Select-Object Name
Invoke-Command SBD-DHCP01.sbd.test { Get-LocalGroupMember Administrators }   # from SBD-ADMIN-SRV01: SBD Tier 1 Admins, no Domain Admins
dsacls 'OU=Workstations,OU=SBD,DC=sbd,DC=test' /G 'SBD\SBD Tier 2 Admins:CC;computer'   # from AdminJumpBox01 (Tier 0)
# On the test client, as its local administrator: join with a Tier 2 account, then, after the restart, its managers
Add-Computer -DomainName sbd.test -Credential (Get-Credential) -Restart
Add-LocalGroupMember Administrators -Member 'SBD\SBD Tier 2 Admins'; Enable-PSRemoting -Force
Set-NetFirewallRule -DisplayGroup 'Windows Remote Management' -RemoteAddress 10.0.60.30
# On the Tier 2 test machine: export the effective logon rights and read the deny lines.
secedit /export /mergedpolicy /cfg C:\Evidence\tiered-admin\user-rights.inf /areas user_rights
Select-String -Path C:\Evidence\tiered-admin\user-rights.inf -Pattern 'SeDeny'
'SBD Tier 0 Admins', 'SBD Tier 1 Admins', 'SBD Audit Custodians' | ForEach-Object { (Get-ADGroup $_).SID.Value }   # from an admin seat: these SIDs must appear on those lines
# Repeat on each seat as its maintenance account, and read who may sign in there.
Select-String -Path C:\Evidence\tiered-admin\user-rights.inf -Pattern 'SeInteractive'
# After the refused Tier 0 sign-in: event 4625 with status 0xC000015B (logon type not granted).
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddHours(-1)} | Where-Object Message -match '0xC000015B' | Select-Object TimeCreated, Id

Verify: Active Directory Users and Computers shows SBD-SUBCA01 and SBD-PKI-MGMT01 in the Tier 0 Servers OU, and they still accept Tier 0 sign-ins; Computer Management on SBD-DHCP01 shows SBD Tier 1 Admins, not Domain Admins, in its local Administrators; on the Tier 2 test machine (part 7's test client, now in the Workstations OU) the Group Policy settings report shows the deny rights naming the Tier 0, Tier 1 and audit custodian groups, and a Tier 0 sign-in there is refused, with Security event 4625 (status 0xC000015B) in Event Viewer. Each admin seat lets only its own groups sign in and denies the other tiers: Tier 0, Tier 1 and the audit custodians inside SBD-ADMIN-PC01, Tier 0 and Tier 2 on SBD-ADMIN-SRV01, Tier 1 and Tier 2 on AdminJumpBox01. The exception record holds the console network-logon exception and the seat GPOs' STIG differences, Protected Users contains the Tier 0 admins, and the break-glass and MFA decisions are written.

Evidence: Keep the Tier 0 Servers OU move, the logon-rights and administrators GPO reports with their links and security filtering, the user-rights exports from the test machine and each seat, the refused sign-in's 4625 event, the exception record entries, the Protected Users membership, and the break-glass and MFA decision records.

Notes / result / date:

45. Prove the tiers end to end

About 30 min · Open this lesson

Commands:

Enter-PSSession SBD-DHCP01.sbd.test   # from SBD-ADMIN-SRV01, Tier 1 account
vmconnect.exe localhost SBD-ADMIN-PC01   # same account, not elevated: the console opens
Test-NetConnection SBD-ADMIN-SRV01.sbd.test -Port 5985   # from the guest: must fail
Test-NetConnection SBD-ADMIN-PC01.sbd.test -Port 5985   # from the host: must fail
ipconfig /renew   # on the VLAN 80 test client
# Where a sign-in was refused: last hour's 0xC000015B failures (logon type not granted).
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddHours(-1)} | Where-Object Message -match '0xC000015B' | Select-Object TimeCreated, Id

Verify: Every in-tier path succeeds, every tested out-of-tier path fails with captured evidence — in both directions across the seat virtual machine's boundary too — and part 7's DHCP service and secure registration still work end to end under the full rule set.

Evidence: Keep the completed tier matrix with timestamps, the denial captures, the host and guest separation results, and the post-restriction lease and registration proof.

Notes / result / date:

46. Move the scan tools, rescan, and hand off

About 30 min · Open this lesson

Commands:

# On AdminJumpBox01 (Tier 0): carry the evidence over; no output means a set matches its manifest
Copy-Item \\SBD-DC01\C$\Evidence -Destination C:\ -Recurse
'baseline', 'hardened', 'pki', 'dhcp' | ForEach-Object { Compare-Object (Import-Csv "C:\Evidence\$_-manifest.csv").Hash (Get-ChildItem "C:\Evidence\$_" -Recurse -File | Get-FileHash -Algorithm SHA256).Hash }
Invoke-Command SBD-DC01, SBD-DC02 { Test-Path 'C:\Program Files*\SCAP Compliance Checker*' }   # False once SCC is removed
# On SBD-ADMIN-SRV01 (Tier 1): part 7's server record, checked the same way
$s = New-PSSession SBD-DHCP01.sbd.test
Copy-Item C:\Evidence\dhcp, C:\Evidence\dhcp-manifest.csv -FromSession $s -Destination C:\Evidence\ -Recurse
Compare-Object (Import-Csv C:\Evidence\dhcp-manifest.csv).Hash (Get-ChildItem C:\Evidence\dhcp -Recurse -File | Get-FileHash -Algorithm SHA256).Hash
# Still on SBD-ADMIN-SRV01, as a Hyper-V administrator: the media set's ISO into the Tier 2 seat, read-only
Set-VMDvdDrive -VMName SBD-ADMIN-PC01 -Path '<part 4 media set ISO>'
# Inside SBD-ADMIN-PC01 (Tier 2), with the ISO as D: the installer matches the record (expected: True); keep both for course 1.6
$scc = Get-Item 'D:\<SCC installer>'
(Get-FileHash $scc.FullName -Algorithm SHA256).Hash -in (Import-Csv D:\connected-side-hashes.csv).Hash
New-Item -ItemType Directory -Force C:\Staging\DISA | Out-Null
Copy-Item $scc.FullName, D:\connected-side-hashes.csv -Destination C:\Staging\DISA\
Set-VMDvdDrive -VMName SBD-ADMIN-PC01 -Path $null   # back on the host, after the install: detach the ISO
# On each seat, after its scans: this part's evidence, hashed
Get-ChildItem C:\Evidence\tiered-admin -Recurse | Select-Object Name, LastWriteTime
Get-FileHash -Algorithm SHA256 C:\Evidence\tiered-admin\*

Verify: SCC and STIG Viewer run from AdminJumpBox01 and are gone from the controllers, every moved evidence set matches its manifest, and each seat has its tier's tools; first baselines for the three seats, SBD-ADMIN-PC01's from its own SCC copy, and rescans of SBD-SUBCA01, SBD-PKI-MGMT01, SBD-DHCP01, and both domain controllers are filed with attributed deltas; SBD-DHCP01's checklist is complete; the exception record and register carry the new rows; and the handoff names the management constraint for course 1.2 and the logging courses.

Evidence: Finish the checklist: the tool and evidence moves with their hash comparisons, SBD-ADMIN-PC01's installer check, the scan set, the updated records, the tier matrix, and the handoff note.

Notes / result / date:

Sources