From 51c2592cda021efc8409d3d132412e6a0105af9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adil=20Alperen=20=C3=87iftci?= <134228585+adilalperenciftci@users.noreply.github.com> Date: Sun, 25 Oct 2026 05:01:58 +0300 Subject: [PATCH 1/3] feat: Collect dMSA patch prerequisites from domain controllers --- src/Runtime/ObjectProcessors.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Runtime/ObjectProcessors.cs b/src/Runtime/ObjectProcessors.cs index 66c975f..7cb0af3 100644 --- a/src/Runtime/ObjectProcessors.cs +++ b/src/Runtime/ObjectProcessors.cs @@ -464,7 +464,9 @@ private async void ProcessDomainController(ResolvedSearchResult resolvedSearchRe StrongCertificateBindingEnforcement = _dcRegistryProcessor.GetStrongCertificateBindingEnforcement(apiName), VulnerableNetlogonSecurityDescriptor = - _dcRegistryProcessor.GetVulnerableNetlogonSecurityDescriptor(apiName) + _dcRegistryProcessor.GetVulnerableNetlogonSecurityDescriptor(apiName), + CurrentBuildNumber = _dcRegistryProcessor.GetCurrentBuildNumber(apiName), + CurrentBuildRevision = _dcRegistryProcessor.GetCurrentBuildRevision(apiName) }; ret.DCRegistryData = dCRegistryData; From 846e4310e489ce2c6f88734fc93c123074f4de62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adil=20Alperen=20=C3=87iftci?= Date: Tue, 27 Oct 2026 15:27:26 +0300 Subject: [PATCH 2/3] fix: await domain controller post-processing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I now await domain controller registry and LDAP processing before returning the collected computer result. Signed-off-by: Adil Alperen Çiftci --- src/Runtime/ObjectProcessors.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Runtime/ObjectProcessors.cs b/src/Runtime/ObjectProcessors.cs index 7cb0af3..3b80b34 100644 --- a/src/Runtime/ObjectProcessors.cs +++ b/src/Runtime/ObjectProcessors.cs @@ -327,7 +327,7 @@ await _containerProcessor.GetContainingObject(dn) is (true, var container)) { } if (resolvedSearchResult.IsDomainController) - ProcessDomainController(resolvedSearchResult, ret, apiName); + await ProcessDomainController(resolvedSearchResult, ret, apiName); var trimmedSamAccountName = samAccountName?.TrimEnd('$'); @@ -453,7 +453,7 @@ private RegistryProcessor GetRegistryProcessor(ResolvedSearchResult resolvedSear })).Value; } - private async void ProcessDomainController(ResolvedSearchResult resolvedSearchResult, Computer ret, + private async Task ProcessDomainController(ResolvedSearchResult resolvedSearchResult, Computer ret, string apiName) { _log.LogDebug("Processing DC: {dc}", apiName); From 43255854cbda249eb063be58f0637d61151957b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adil=20Alperen=20=C3=87iftci?= Date: Tue, 27 Oct 2026 16:05:17 +0300 Subject: [PATCH 3/3] feat: emit delegated managed service accounts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I now process delegated managed service accounts as typed objects, resolve their predecessor links, and write them to a dedicated collection file. Signed-off-by: Adil Alperen Çiftci --- src/Runtime/ObjectProcessors.cs | 42 +++++++++++++++++++++++++++++++++ src/Runtime/OutputWriter.cs | 9 ++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Runtime/ObjectProcessors.cs b/src/Runtime/ObjectProcessors.cs index 3b80b34..c54fe14 100644 --- a/src/Runtime/ObjectProcessors.cs +++ b/src/Runtime/ObjectProcessors.cs @@ -137,6 +137,8 @@ internal async Task ProcessObject(IDirectoryObject entry, ResolvedSe return await ProcessSiteServerObject(entry, resolvedSearchResult); case Label.SiteSubnet: return await ProcessSiteSubnetObject(entry, resolvedSearchResult); + case Label.DMSA: + return await ProcessDMSAObject(entry, resolvedSearchResult); case Label.Base: return null; default: @@ -248,6 +250,46 @@ await _containerProcessor.GetContainingObject(dn) is (true, var container)) return ret; } + private async Task ProcessDMSAObject(IDirectoryObject entry, + ResolvedSearchResult resolvedSearchResult) { + var ret = new DMSA { + ObjectIdentifier = resolvedSearchResult.ObjectId, + Properties = new Dictionary(GetCommonProperties(entry, resolvedSearchResult)) { + { "samaccountname", entry.GetProperty(LDAPProperties.SAMAccountName) } + } + }; + + if (_methods.HasFlag(CollectionMethod.ACL)) { + var aces = await _aclProcessor.ProcessACL(resolvedSearchResult, entry, true) + .ToArrayAsync(cancellationToken: _cancellationToken); + ret.Properties.Add("doesanyacegrantownerrights", aces.Any(ace => ace.IsPermissionForOwnerRightsSid)); + ret.Properties.Add("doesanyinheritedacegrantownerrights", + aces.Any(ace => ace.IsInheritedPermissionForOwnerRightsSid)); + ret.Aces = aces; + ret.IsACLProtected = _aclProcessor.IsACLProtected(entry); + ret.Properties.Add("isaclprotected", ret.IsACLProtected); + } + + if (_methods.HasFlag(CollectionMethod.ObjectProps)) { + if (entry.TryGetProperty(LDAPProperties.ManagedAccountPrecededByLink, out var predecessor) && + await _context.LDAPUtils.ResolveDistinguishedName(predecessor) is (true, var principal)) { + ret.PrecededBy = principal; + } + + if (_context.Flags.CollectAllProperties) { + ret.Properties = ContextUtils.Merge(_ldapPropertyProcessor.ParseAllProperties(entry), + ret.Properties); + } + } + + if (_methods.HasFlag(CollectionMethod.Container) && + await _containerProcessor.GetContainingObject(entry) is (true, var container)) { + ret.ContainedBy = container; + } + + return ret; + } + private async Task ProcessComputerObject( IDirectoryObject entry, ResolvedSearchResult resolvedSearchResult diff --git a/src/Runtime/OutputWriter.cs b/src/Runtime/OutputWriter.cs index 32c2aa1..0e65cd3 100644 --- a/src/Runtime/OutputWriter.cs +++ b/src/Runtime/OutputWriter.cs @@ -37,6 +37,7 @@ public class OutputWriter private readonly JsonDataWriter _siteOutput; private readonly JsonDataWriter _siteServerOutput; private readonly JsonDataWriter _siteSubnetOutput; + private readonly JsonDataWriter _dmsaOutput; private int _completedCount; @@ -63,6 +64,7 @@ public OutputWriter(IContext context, Channel outputChannel) _siteOutput = new JsonDataWriter(_context, DataType.Sites); _siteServerOutput = new JsonDataWriter(_context, DataType.SiteServers); _siteSubnetOutput = new JsonDataWriter(_context, DataType.SiteSubnets); + _dmsaOutput = new JsonDataWriter(_context, DataType.DMSAs); _runTimer = new Stopwatch(); _statusTimer = new Timer(_context.StatusInterval); @@ -160,6 +162,9 @@ internal async Task StartWriter() case SiteSubnet siteSubnet: await _siteSubnetOutput.AcceptObject(siteSubnet); break; + case DMSA dmsa: + await _dmsaOutput.AcceptObject(dmsa); + break; default: throw new ArgumentOutOfRangeException(nameof(item)); } @@ -187,6 +192,7 @@ private async Task FlushWriters() await _siteOutput.FlushWriter(); await _siteServerOutput.FlushWriter(); await _siteSubnetOutput.FlushWriter(); + await _dmsaOutput.FlushWriter(); CloseOutput(); var fileName = ZipFiles(); return fileName; @@ -218,6 +224,7 @@ private string ZipFiles() _enterpriseCAOutput.GetFilename(), _nTAuthStoreOutput.GetFilename(), _certTemplateOutput.GetFilename(), _issuancePolicyOutput.GetFilename(), _siteOutput.GetFilename(), _siteServerOutput.GetFilename(), _siteSubnetOutput.GetFilename(), + _dmsaOutput.GetFilename(), }); foreach (var entry in fileList.Where(x => !string.IsNullOrEmpty(x))) @@ -245,4 +252,4 @@ private string ZipFiles() return resolvedFileName; } } -} \ No newline at end of file +}