[AD 2.1] Build Sheet: The AD Dependency Inventory

Turn on the instruments, run them for thirty days, and end Stage 0 with a dependency inventory that has zero unknowns: NTLM and LDAP auditing, the Kerberos consumer census, the account sweep, and the planted test that proves your silence is real.


By the end of this you have every instrument from the doctrine article switched on, a thirty-day observation window running across a month-end, a planted authentication that proves the instruments catch what they claim to catch, and an inventory where every dependency has a name, an owner, and a disposition. Commands appear only where the console cannot do the job in one obvious move. Most of these steps are ten minutes of work followed by weeks of patience, which is the correct ratio for a census.


Before Step 1: Domain Admin on DC01 and DC02, local admin on FS01, NPS01 and SQL01, the Group Policy Management console, and a place to keep the inventory. A spreadsheet is fine. Give each finding a row named PIN-<class>-<nn>, so the first printer’s LDAP habit becomes PIN-LDAP-01 and the nightly export job becomes PIN-SVC-01, and carry four columns: what it is, who owns it, what stage handles it, and its disposition.

DispositionMeaningHandled in
MoveThe dependency follows its workload to a cloud serviceStages 1 to 3
ReplaceThe dependency is retired in favor of something directory-freeStage 3
IsolateThe dependency keeps working with local credentials on a segmented networkStage 4
KeepThe dependency genuinely requires a directory, stated in writing with a review dateStage 4, honestly

Step 1: Turn on NTLM auditing

Edit the Default Domain Controllers Policy and set two values under Security Options. These are audit settings, not restrictions; nothing changes for users.

SettingValueWhy
Network security: Restrict NTLM: Audit NTLM authentication in this domainEnable allLogs every NTLM authentication a DC processes for domain accounts
Network security: Restrict NTLM: Audit Incoming NTLM TrafficEnable auditing for all accountsCatches NTLM arriving at the DCs themselves

The events land in an easily missed place: Applications and Services Logs, then Microsoft, Windows, NTLM, Operational, not the Security log. On the domain controllers, event 8004 is the one that matters: each entry names the account and the workstation that offered NTLM. It will not tell you the process or the target service, and the workstation field is client-supplied, so treat it as a lead and confirm the source before you write the row. Your Windows 11 24H2 machines fill that gap from the client side, logging which process fell back to NTLM and a reason code saying why. If a source keeps its secrets, the catch-all is the Netlogon debug log, which names the resource server the authentication was for:

nltest /dbflag:2080FFFF
:: read %windir%\debug\netlogon.log for SamLogon lines, then turn it back off:
nltest /dbflag:0x0

Step 2: Turn on LDAP bind logging

The directory service log already emits event 2887 once a day, a bare count of unsigned and cleartext binds. A count is not an inventory. Raise the diagnostic level on both DCs and each bind becomes event 2889, carrying the client IP and the identity it bound with:

Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' `
  -Name '16 LDAP Interface Events' -Value 2

Know what this instrument measures: 2889 records only the legacy binds, the unsigned and cleartext ones, which is exactly the population you are hunting, because a client binding that way cannot follow you to a world without LDAP. This is where printers, NAS boxes and badge systems confess, by IP address. Leave the level at 2 for the whole window and set it back to 0 afterward, because it is chatty.

Step 3: The Kerberos consumer census

Every Kerberos application in the estate requests service tickets, and each request is event 4769 on a DC’s Security log, naming the service and the client address. Confirm the audit subcategory is on, then pull the census: which services are consumed, and by whom.

auditpol /get /subcategory:"Kerberos Service Ticket Operations"
auditpol /set /subcategory:"Kerberos Service Ticket Operations" /success:enable
Get-WinEvent -FilterHashtable @{LogName='Security';Id=4769} -MaxEvents 50000 |
  ForEach-Object { $x = [xml]$_.ToXml()
    [pscustomobject]@{
      Service = ($x.Event.EventData.Data | Where-Object Name -eq 'ServiceName').'#text'
      Client  = ($x.Event.EventData.Data | Where-Object Name -eq 'IpAddress').'#text'
      EncType = ($x.Event.EventData.Data | Where-Object Name -eq 'TicketEncryptionType').'#text' } } |
  Group-Object Service | Sort-Object Count -Descending |
  Select-Object Count, Name -First 25

Read it two ways. Grouped by service, it ranks what the domain is actually doing all day. Filtered to one service and grouped by client, it names that service’s consumers, which is the list you will need when the service moves. An encryption type of 0x17 flags a client still negotiating RC4, which is a legacy marker worth its own inventory row.

Step 4: The file server’s own testimony

On FS01, sample who is connected and what they touch across both the departmental shares and \\FS01\scans, and sweep the directory for the attributes that hard-wire users to the server. Run the sampling a few times across the window at different hours; sessions are a point-in-time answer.

Get-SmbSession | Select-Object ClientComputerName, ClientUserName, Dialect
Get-SmbOpenFile | Group-Object ShareRelativePath |
  Sort-Object Count -Descending | Select-Object Count, Name -First 20

Get-ADUser -Filter * -Properties homeDirectory, profilePath, scriptPath |
  Where-Object { $_.homeDirectory -or $_.profilePath -or $_.scriptPath } |
  Select-Object SamAccountName, homeDirectory, profilePath, scriptPath

The Dialect column earns its place: anything below 3 is an old client, and anything at 1 is a finding all by itself. Folder redirection does not live in these attributes; it lives in Group Policy, and Step 8 catches it.

Step 5: What the SQL box says

SnackTrack’s dependency is not a matter of opinion. SQL Server reports the authentication scheme of every live connection:

SELECT s.login_name, s.host_name, s.program_name, c.auth_scheme
FROM sys.dm_exec_connections c
JOIN sys.dm_exec_sessions s ON s.session_id = c.session_id;

Run it while the application is in use, and run it from a real client workstation, because local connections always report NTLM regardless of how the estate actually authenticates. KERBEROS or NTLM in auth_scheme means the domain is in the authentication path and this application belongs in Stage 4’s conversation. SQL means it never was, and one dependency you were sure of evaporates on the spot.

Step 6: The account census

The running census in Steps 1 and 3 catches accounts in use. This step catches the configured ones, including the job that only runs on the last day of the month. Run it on each member server:

Get-CimInstance Win32_Service |
  Where-Object { $_.StartName -like 'CATSNACKJACK\*' } |
  Select-Object Name, StartName, State

Get-ScheduledTask |
  Where-Object { $_.Principal.UserId -like 'CATSNACKJACK\*' } |
  Select-Object TaskName, @{n='RunsAs';e={$_.Principal.UserId}}

Get-ADServiceAccount -Filter * -Properties PrincipalsAllowedToRetrieveManagedPassword

Any group managed service account is a hard pin on its own: only a domain controller can serve its password, so every gMSA consumer goes on the inventory with no exceptions. If you hold E5, Defender for Identity’s service account discovery draws this map for you with sources and destinations attached; use it as corroboration, not as a substitute for the configured-state sweep.

Step 7: What NPS answers for

No commands needed. Open the Security log on NPS01 and filter to events 6272 and 6273, granted and denied. Each entry names the RADIUS client that asked, which is your map of every access point and switch that depends on NPS, and 6273’s reason code explains each denial. The auditing is on by default; if the log is empty while the Wi-Fi works, that absence is a finding, because it means something other than NPS is authenticating your wireless and the inventory just got more interesting.

Step 8: What Group Policy actually does

Export everything once, then let Intune grade it. Group Policy analytics takes a per-GPO XML import and returns, setting by setting, what has a cloud equivalent and what does not.

Get-GPOReport -All -ReportType Xml -Path C:\temp\gpo-inventory.xml

Read the results with the right expectations. The percentages triage; they do not decide. Group Policy Preferences items are parsed but overwhelmingly come back not supported, because no CSP exists for most of them; that does not mean the need is unmeetable, it means the replacement is a different mechanism, drive mappings becoming SharePoint libraries rather than a mapped-drive policy. Grep the XML export for the pins the analytics will not flag as pins: redirected folder paths, logon script names, UNC references. Every GPO ends the window classified as doing a live job, duplicating what Intune already does, or doing nothing, and only the first class generates inventory rows.

Step 9: What the CA issued

The certificate authority can enumerate its own obligations. List the templates it offers, then census what it actually issued and when those certificates expire:

certutil -CATemplates
certutil -view -restrict "Disposition=20" -out "RequestID,RequesterName,CertificateTemplate,NotAfter" > C:\temp\issued.txt

Disposition 20 means issued. Aggregate the output by template and you have your certificate populations: at CatSnackJack, device certificates for the Wi-Fi and machine certificates from autoenrollment, each population an inventory row whose disposition gets decided in Stage 3. This step only counts what exists; the retirement of the authority itself already has its own build sheet in the PKI series, and this inventory is deliberately its prerequisite.

Step 10: The network plumbing sweep

Last, find everything pointed at the DCs for services nobody associates with the domain:

Get-DhcpServerv4Scope |
  ForEach-Object { Get-DhcpServerv4OptionValue -ScopeId $_.ScopeId -OptionId 6 }
Get-DhcpServerDnsCredential
Get-DnsServerZone | Select-Object ZoneName, ZoneType, IsDsIntegrated, DynamicUpdate

Option 6 tells you which DNS servers every scope hands out, and at most SMBs the answer is the domain controllers, which means every device on the network has a dependency on machines you intend to power off. Add the statically configured stragglers: servers, printers, and anything with a reserved address gets its DNS settings checked by hand. None of this is hard to fix later, and all of it is miserable to discover later.

The thirty-day window, and what the estate told us

Run everything above across thirty days including a month-end. Here is what CatSnackJack’s instruments returned, and the reading matters more than the collecting. Event 2889 showed two IP addresses binding every night at 02:00, 10.1.20.31 and 10.1.20.32, simple binds as a named account: MFP-01 and MFP-02 refreshing their address books in cleartext, exactly as predicted and now proven rather than assumed. Event 8004 caught svc-scan authenticating from the copiers and, more usefully, SnackTrack’s application pool authenticating to SQL01 by IP address, which is why it fell back to NTLM. The 4769 census was dominated by two services, the SQL service on SQL01 and host tickets for FS01, which settles the sequencing: SnackTrack is the longest pole in the estate and its disposition drives the schedule, so Stage 4’s decision work starts now rather than in month six. The SMB session sampling found nine machines still touching redirected folders on FS01, plus steady traffic to the departmental shares and the scans share. The NPS log named four access points depending on it for 802.1X, and the print queues showed up in the same sweep. The GPO export graded twenty-three GPOs down to four that carry live settings, and the monthly billing export appeared on day 29, running as a domain account nobody had thought about since 2021, which is the entire argument for the month-end rule in one log entry.

Prove the instruments, then stand down the noisy ones

Before trusting any silence, plant one authentication you control and confirm it is caught. Connect to a share by IP address, which forces NTLM because Kerberos needs a name to build a ticket against:

net use \\10.1.10.25\finance /user:CATSNACKJACK\aaron *

Event 8004 should record it within minutes, and the next nightly printer bind should appear as 2889 on schedule. Both observed is the pass. An instrument that never catches anything might mean a clean estate, and it might mean a setting that did not land; only the planted test tells you which. When the window closes, set the LDAP diagnostic level back to 0 and make sure the Netlogon debug flag is off. Leave the NTLM auditing on permanently; it costs nearly nothing and it should have been on anyway.

Where existing tools help, and where they do not

Two community tools shorten this work. GPOZaurr, an MIT-licensed PowerShell module that is actively maintained, does Step 8’s live-versus-stale grading with prebuilt reports and will find the empty, unlinked and orphaned GPOs faster than reading the XML export; run it alongside the analytics import rather than instead of it, because the analytics grading against cloud equivalents is the half GPOZaurr does not do. Microsoft’s Event1644Reader turns the full LDAP query census into pivot tables when you need depth beyond the legacy binds that 2889 catches. Be equally clear about what does not help here: the security posture tools, PingCastle and Purple Knight, answer whether the domain is configured safely, which is a different question from who still depends on it. They will tell you NTLMv1 is permitted and never tell you who is using it. Run them for hardening. Nothing free assembles this census end to end, which is why the steps above exist.

The gate, restated

Stage 0 is complete when the inventory has a row for every finding with a name, an owner, a handling stage and one of the four dispositions, when every instrument has caught its planted test, and when nothing on the list reads unknown. CatSnackJack closed at nineteen rows: the two printer binds, svc-scan from the copiers, SnackTrack’s application pool and its SQL service, the nightly export, the departmental shares and the scans share, the print queues, the home-drive attributes, four live GPOs, two certificate populations, NPS and the 802.1X it answers for, DHCP’s option 6, and the DNS zones themselves. Every one of them was knowable in advance, and now every one of them is known. Stage 1 cuts identity over with that list on the table, which is the difference between a migration and a gamble.


AD to Azure
‹ Previous: [AD 2] What Is Actually Pinning Your Domain
Next: [AD 2.2] The Census, Automated