File: System\DirectoryServices\ActiveDirectory\ConfigSet.cs
Web Access
Project: src\src\runtime\src\libraries\System.DirectoryServices\src\System.DirectoryServices.csproj (System.DirectoryServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace System.DirectoryServices.ActiveDirectory
{
    public class ConfigurationSet
    {
        // Private Variables
        private readonly DirectoryContext _context;
        private readonly DirectoryEntryManager _directoryEntryMgr;
        private bool _disposed;

        // variables corresponding to public properties
        private readonly string _configSetName;
        private ReadOnlySiteCollection? _cachedSites;
        private AdamInstanceCollection? _cachedADAMInstances;
        private ApplicationPartitionCollection? _cachedApplicationPartitions;
        private ActiveDirectorySchema? _cachedSchema;
        private AdamInstance? _cachedSchemaRoleOwner;
        private AdamInstance? _cachedNamingRoleOwner;
        private ReplicationSecurityLevel _cachedSecurityLevel = (ReplicationSecurityLevel)(-1);

        // 4 minutes timeout for locating an ADAM instance in the configset
        private static readonly TimeSpan s_locationTimeout = new TimeSpan(0, 4, 0);

        #region constructors
        internal ConfigurationSet(DirectoryContext context, string configSetName, DirectoryEntryManager directoryEntryMgr)
        {
            _context = context;
            _configSetName = configSetName;
            _directoryEntryMgr = directoryEntryMgr;
        }

        internal ConfigurationSet(DirectoryContext context, string configSetName)
            : this(context, configSetName, new DirectoryEntryManager(context))
        {
        }
        #endregion constructors

        #region IDisposable

        public void Dispose() => Dispose(true);

        // private Dispose method
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                // check if this is an explicit Dispose
                // only then clean up the directory entries
                if (disposing)
                {
                    // dispose all directory entries
                    foreach (DirectoryEntry entry in _directoryEntryMgr.GetCachedDirectoryEntries())
                    {
                        entry.Dispose();
                    }
                }
                _disposed = true;
            }
        }
        #endregion IDisposable

        #region public methods

        public static ConfigurationSet GetConfigurationSet(DirectoryContext context)
        {
            // check that the argument is not null
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            // target should ConfigurationSet or DirectoryServer
            if ((context.ContextType != DirectoryContextType.ConfigurationSet) &&
                (context.ContextType != DirectoryContextType.DirectoryServer))
            {
                throw new ArgumentException(SR.TargetShouldBeServerORConfigSet, nameof(context));
            }

            // target should be an adam config set or server
            if (((!context.isServer()) && (!context.isADAMConfigSet())))
            {
                // the target should be a server or an ADAM Config Set
                if (context.ContextType == DirectoryContextType.ConfigurationSet)
                {
                    throw new ActiveDirectoryObjectNotFoundException(SR.ConfigSetNotFound, typeof(ConfigurationSet), context.Name);
                }
                else
                {
                    throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.AINotFound, context.Name), typeof(ConfigurationSet), null);
                }
            }

            //  work with copy of the context
            context = new DirectoryContext(context);

            //
            // bind to rootdse of an adam instance (if target is already a server, verify that it is an adam instance)
            //
            DirectoryEntryManager directoryEntryMgr = new DirectoryEntryManager(context);
            DirectoryEntry? rootDSE = null;
            string configSetName;

            try
            {
                rootDSE = directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.RootDSE);
                if ((context.isServer()) && (!Utils.CheckCapability(rootDSE, Capability.ActiveDirectoryApplicationMode)))
                {
                    throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.AINotFound, context.Name), typeof(ConfigurationSet), null);
                }

                configSetName = (string)PropertyManager.GetPropertyValue(context, rootDSE, PropertyManager.ConfigurationNamingContext)!;
            }
            catch (COMException e)
            {
                int errorCode = e.ErrorCode;

                if (errorCode == unchecked((int)0x8007203a))
                {
                    if (context.ContextType == DirectoryContextType.ConfigurationSet)
                    {
                        throw new ActiveDirectoryObjectNotFoundException(SR.ConfigSetNotFound, typeof(ConfigurationSet), context.Name);
                    }
                    else
                    {
                        throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.AINotFound, context.Name), typeof(ConfigurationSet), null);
                    }
                }
                else
                {
                    throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }
            }
            catch (ActiveDirectoryObjectNotFoundException)
            {
                if (context.ContextType == DirectoryContextType.ConfigurationSet)
                {
                    // this is the case when we could not find an ADAM instance in that config set
                    throw new ActiveDirectoryObjectNotFoundException(SR.ConfigSetNotFound, typeof(ConfigurationSet), context.Name);
                }
                else
                    throw;
            }

            // return config set object
            return new ConfigurationSet(context, configSetName, directoryEntryMgr);
        }

        public AdamInstance FindAdamInstance()
        {
            CheckIfDisposed();
            return FindOneAdamInstance(Name, _context, null, null);
        }

        public AdamInstance FindAdamInstance(string partitionName)
        {
            CheckIfDisposed();

            ArgumentNullException.ThrowIfNull(partitionName);

            return FindOneAdamInstance(Name, _context, partitionName, null);
        }

        public AdamInstance FindAdamInstance(string? partitionName, string siteName)
        {
            CheckIfDisposed();

            //
            // null partitionName would signify that we don't care about the partition
            //

            ArgumentNullException.ThrowIfNull(siteName);

            return FindOneAdamInstance(Name, _context, partitionName, siteName);
        }

        public AdamInstanceCollection FindAllAdamInstances()
        {
            CheckIfDisposed();

            return FindAdamInstances(_context, null, null);
        }

        public AdamInstanceCollection FindAllAdamInstances(string partitionName)
        {
            CheckIfDisposed();

            ArgumentNullException.ThrowIfNull(partitionName);

            return FindAdamInstances(_context, partitionName, null);
        }

        public AdamInstanceCollection FindAllAdamInstances(string? partitionName, string siteName)
        {
            CheckIfDisposed();

            //
            // null partitionName would signify that we don't care about the partition
            //

            ArgumentNullException.ThrowIfNull(siteName);

            return FindAdamInstances(_context, partitionName, siteName);
        }

        public DirectoryEntry GetDirectoryEntry()
        {
            CheckIfDisposed();
            return DirectoryEntryManager.GetDirectoryEntry(_context, WellKnownDN.ConfigurationNamingContext);
        }

        public ReplicationSecurityLevel GetSecurityLevel()
        {
            CheckIfDisposed();
            if (_cachedSecurityLevel == (ReplicationSecurityLevel)(-1))
            {
                DirectoryEntry configEntry = _directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.ConfigurationNamingContext);
                _cachedSecurityLevel = (ReplicationSecurityLevel)((int)PropertyManager.GetPropertyValue(_context, configEntry, PropertyManager.MsDSReplAuthenticationMode)!);
            }
            return _cachedSecurityLevel;
        }

        public void SetSecurityLevel(ReplicationSecurityLevel securityLevel)
        {
            CheckIfDisposed();
            if (securityLevel < ReplicationSecurityLevel.NegotiatePassThrough || securityLevel > ReplicationSecurityLevel.MutualAuthentication)
            {
                throw new InvalidEnumArgumentException(nameof(securityLevel), (int)securityLevel, typeof(ReplicationSecurityLevel));
            }

            try
            {
                DirectoryEntry configEntry = _directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.ConfigurationNamingContext);
                configEntry.Properties[PropertyManager.MsDSReplAuthenticationMode].Value = (int)securityLevel;
                configEntry.CommitChanges();
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(_context, e);
            }

            // invalidate the cached entry
            _cachedSecurityLevel = (ReplicationSecurityLevel)(-1);
        }

        public override string ToString() => Name;

        #endregion public methods

        #region public properties

        public string Name
        {
            get
            {
                CheckIfDisposed();
                return _configSetName;
            }
        }

        public ReadOnlySiteCollection Sites
        {
            get
            {
                CheckIfDisposed();
                return _cachedSites ??= new ReadOnlySiteCollection(GetSites());
            }
        }

        public AdamInstanceCollection AdamInstances
        {
            get
            {
                CheckIfDisposed();
                return _cachedADAMInstances ??= FindAllAdamInstances();
            }
        }

        public ApplicationPartitionCollection ApplicationPartitions
        {
            get
            {
                CheckIfDisposed();
                return _cachedApplicationPartitions ??= new ApplicationPartitionCollection(GetApplicationPartitions());
            }
        }

        public ActiveDirectorySchema Schema
        {
            get
            {
                CheckIfDisposed();
                if (_cachedSchema == null)
                {
                    try
                    {
                        _cachedSchema = new ActiveDirectorySchema(_context, _directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.SchemaNamingContext));
                    }
                    catch (COMException e)
                    {
                        throw ExceptionHelper.GetExceptionFromCOMException(_context, e);
                    }
                }
                return _cachedSchema;
            }
        }

        public AdamInstance SchemaRoleOwner
        {
            get
            {
                CheckIfDisposed();
                return _cachedSchemaRoleOwner ??= GetRoleOwner(AdamRole.SchemaRole);
            }
        }

        public AdamInstance NamingRoleOwner
        {
            get
            {
                CheckIfDisposed();
                return _cachedNamingRoleOwner ??= GetRoleOwner(AdamRole.NamingRole);
            }
        }

        #endregion public properties

        #region private methods

        private static DirectoryEntry GetSearchRootEntry(Forest forest)
        {
            DirectoryEntry rootEntry;
            DirectoryContext forestContext = forest.GetDirectoryContext();
            bool isServer = false;
            bool isGC = false;
            AuthenticationTypes authType = Utils.DefaultAuthType;

            if (forestContext.ContextType == DirectoryContextType.DirectoryServer)
            {
                //
                // the forest object was created by specifying a server name
                // so we will stick to that server for the search. We need to determine
                // whether or not the server is a DC or GC
                //
                isServer = true;
                DirectoryEntry rootDSE = DirectoryEntryManager.GetDirectoryEntry(forestContext, WellKnownDN.RootDSE);
                string? isGCReady = (string?)PropertyManager.GetPropertyValue(forestContext, rootDSE, PropertyManager.IsGlobalCatalogReady);
                isGC = (Utils.Compare(isGCReady, "TRUE") == 0);
            }

            if (isServer)
            {
                authType |= AuthenticationTypes.ServerBind;

                if (isGC)
                {
                    rootEntry = new DirectoryEntry("GC://" + forestContext.GetServerName(), forestContext.UserName, forestContext.Password, authType);
                }
                else
                {
                    rootEntry = new DirectoryEntry("LDAP://" + forestContext.GetServerName(), forestContext.UserName, forestContext.Password, authType);
                }
            }
            else
            {
                // need to find any GC in the forest
                rootEntry = new DirectoryEntry("GC://" + forest.Name, forestContext.UserName, forestContext.Password, authType);
            }

            return rootEntry;
        }

        internal static AdamInstance FindAnyAdamInstance(DirectoryContext context)
        {
            if (context.ContextType != DirectoryContextType.ConfigurationSet)
            {
                // assuming it's an ADAM Instance
                // check that it is an ADAM server only (not AD)
                DirectoryEntryManager directoryEntryMgr = new DirectoryEntryManager(context);
                DirectoryEntry rootDSE = directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.RootDSE);

                if (!Utils.CheckCapability(rootDSE, Capability.ActiveDirectoryApplicationMode))
                {
                    directoryEntryMgr.RemoveIfExists(directoryEntryMgr.ExpandWellKnownDN(WellKnownDN.RootDSE));
                    throw new ArgumentException(SR.TargetShouldBeServerORConfigSet, nameof(context));
                }

                string dnsHostName = (string?)PropertyManager.GetPropertyValue(context, rootDSE, PropertyManager.DnsHostName)!;

                return new AdamInstance(context, dnsHostName, directoryEntryMgr);
            }

            // Now this is the case where context is a Config Set
            // Here we need to search for the service connection points in the forest
            // (if the forest object was created by specifying the server, we stick to that, else search in a GC)
            DirectoryEntry rootEntry = GetSearchRootEntry(Forest.GetCurrentForest());
            ArrayList adamInstanceNames = new ArrayList();

            try
            {
                // Search for computer "serviceConnectionObjects" where the keywords attribute
                // contains the specified keyword
                // set up the searcher object

                // build the filter
                StringBuilder str = new StringBuilder(15);
                str.Append("(&(");
                str.Append(PropertyManager.ObjectCategory);
                str.Append("=serviceConnectionPoint)");
                str.Append('(');
                str.Append(PropertyManager.Keywords);
                str.Append("=1.2.840.113556.1.4.1851)(");
                str.Append(PropertyManager.Keywords);
                str.Append('=');
                str.Append(Utils.GetEscapedFilterValue(context.Name!)); // target = config set name
                str.Append("))");

                string filter = str.ToString();
                string[] propertiesToLoad = new string[1];

                propertiesToLoad[0] = PropertyManager.ServiceBindingInformation;

                ADSearcher searcher = new ADSearcher(rootEntry, filter, propertiesToLoad, SearchScope.Subtree, false /*not paged search*/, false /*no cached results*/);
                SearchResultCollection resCol = searcher.FindAll();

                try
                {
                    foreach (SearchResult res in resCol)
                    {
                        // the binding info contains two values
                        // "ldap://hostname:ldapport"
                        // and "ldaps://hostname:sslport"
                        // we need the "hostname:ldapport" value
                        string prefix = "ldap://";

                        foreach (string bindingInfo in res.Properties[PropertyManager.ServiceBindingInformation])
                        {
                            if ((bindingInfo.Length > prefix.Length) && (string.Equals(bindingInfo.Substring(0, prefix.Length), prefix, StringComparison.OrdinalIgnoreCase)))
                            {
                                adamInstanceNames.Add(bindingInfo.Substring(prefix.Length));
                            }
                        }
                    }
                }
                finally
                {
                    resCol.Dispose();
                }
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(context, e);
            }
            finally
            {
                rootEntry.Dispose();
            }

            //
            // we have all the adam instance names in teh form of server:port from the scp
            // now we need to find one that is alive
            //
            return FindAliveAdamInstance(null, context, adamInstanceNames);
        }

        internal static AdamInstance FindOneAdamInstance(DirectoryContext context, string? partitionName, string? siteName)
        {
            return FindOneAdamInstance(null, context, partitionName, siteName);
        }

        internal static AdamInstance FindOneAdamInstance(string? configSetName, DirectoryContext context, string? partitionName, string? siteName)
        {
            // can expect valid context (non-null)
            if (partitionName != null && partitionName.Length == 0)
            {
                throw new ArgumentException(SR.EmptyStringParameter, nameof(partitionName));
            }

            if (siteName != null && siteName.Length == 0)
            {
                throw new ArgumentException(SR.EmptyStringParameter, nameof(siteName));
            }

            ArrayList ntdsaNames = Utils.GetReplicaList(context, partitionName, siteName, false /* isDefaultNC */, true /* isADAM */, false /* mustBeGC */);

            if (ntdsaNames.Count < 1)
            {
                throw new ActiveDirectoryObjectNotFoundException(SR.ADAMInstanceNotFound, typeof(AdamInstance), null);
            }

            return FindAliveAdamInstance(configSetName, context, ntdsaNames);
        }

        internal static AdamInstanceCollection FindAdamInstances(DirectoryContext context, string? partitionName, string? siteName)
        {
            // can expect valid context (non-null)
            if (partitionName != null && partitionName.Length == 0)
            {
                throw new ArgumentException(SR.EmptyStringParameter, nameof(partitionName));
            }

            if (siteName != null && siteName.Length == 0)
            {
                throw new ArgumentException(SR.EmptyStringParameter, nameof(siteName));
            }

            ArrayList adamInstanceList = new ArrayList();

            foreach (string adamInstanceName in Utils.GetReplicaList(context, partitionName, siteName, false /* isDefaultNC */, true /* isADAM */, false /* mustBeGC */))
            {
                DirectoryContext adamInstContext = Utils.GetNewDirectoryContext(adamInstanceName, DirectoryContextType.DirectoryServer, context);
                adamInstanceList.Add(new AdamInstance(adamInstContext, adamInstanceName));
            }

            return new AdamInstanceCollection(adamInstanceList);
        }

        //
        // The input to this function is a list of adam instance names in the form server:port
        // This function tries to bind to each of the instances in this list sequentially until one of the following occurs:
        // 1.  An ADAM instance responds to an ldap_bind - we return an ADAMInstance object for that adam instance
        // 2.  We exceed the timeout duration - we return an ActiveDirectoryObjectNotFoundException
        //
        internal static AdamInstance FindAliveAdamInstance(string? configSetName, DirectoryContext context, ArrayList adamInstanceNames)
        {
            bool foundAliveADAMInstance = false;
            AdamInstance? adamInstance = null;

            // record the start time so that we can determine if the timeout duration has been exceeded or not
            DateTime startTime = DateTime.UtcNow;

            // loop through each adam instance and try to bind to the rootdse
            foreach (string adamInstanceName in adamInstanceNames)
            {
                DirectoryContext adamInstContext = Utils.GetNewDirectoryContext(adamInstanceName, DirectoryContextType.DirectoryServer, context);
                DirectoryEntryManager directoryEntryMgr = new DirectoryEntryManager(adamInstContext);
                DirectoryEntry tempRootEntry = directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.RootDSE);

                try
                {
                    tempRootEntry.Bind(true);
                    adamInstance = new AdamInstance(adamInstContext, adamInstanceName, directoryEntryMgr, true /* nameIncludesPort */);
                    foundAliveADAMInstance = true;
                }
                catch (COMException e)
                {
                    // if this is server down /server busy / server unavailable / timeout  exception we should just eat this up and try the next one
                    if ((e.ErrorCode == unchecked((int)0x8007203a)) ||
                        (e.ErrorCode == unchecked((int)0x8007200e)) ||
                        (e.ErrorCode == unchecked((int)0x8007200f)) ||
                        (e.ErrorCode == unchecked((int)0x800705b4)))
                    {
                        // if we are passed the timeout period, we should throw, else do nothing
                        if (DateTime.UtcNow.Subtract(startTime) > s_locationTimeout)
                            throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.ADAMInstanceNotFoundInConfigSet, configSetName ?? context.Name), typeof(AdamInstance), null);
                    }
                    else
                        throw ExceptionHelper.GetExceptionFromCOMException(context, e);
                }

                if (foundAliveADAMInstance)
                {
                    return adamInstance!;
                }
            }

            // if we reach here, we haven't found an adam instance
            throw new ActiveDirectoryObjectNotFoundException(SR.Format(SR.ADAMInstanceNotFoundInConfigSet, configSetName ?? context.Name), typeof(AdamInstance), null);
        }

        /// <returns>Returns a DomainController object for the DC that holds the specified FSMO role</returns>
        private AdamInstance GetRoleOwner(AdamRole role)
        {
            DirectoryEntry? entry = null;

            string? adamInstName = null;
            try
            {
                switch (role)
                {
                    case AdamRole.SchemaRole:
                        {
                            entry = _directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.SchemaNamingContext);
                            break;
                        }

                    case AdamRole.NamingRole:
                        {
                            entry = _directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.PartitionsContainer);
                            break;
                        }

                    default:
                        // should not happen since we are calling this only internally
                        Debug.Fail("ConfigurationSet.GetRoleOwner: Invalid role type.");
                        break;
                }
                entry.RefreshCache();
                adamInstName = Utils.GetAdamDnsHostNameFromNTDSA(_context, (string)PropertyManager.GetPropertyValue(_context, entry, PropertyManager.FsmoRoleOwner)!);
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(_context, e);
            }
            finally
            {
                entry?.Dispose();
            }

            // create a new context object for the adam instance passing on  the
            // credentials from the context
            DirectoryContext adamInstContext = Utils.GetNewDirectoryContext(adamInstName, DirectoryContextType.DirectoryServer, _context);

            return new AdamInstance(adamInstContext, adamInstName);
        }

        private ArrayList GetSites()
        {
            ArrayList sites = new ArrayList();
            DirectoryEntry sitesEntry = _directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.SitesContainer);

            // search for all the "site" objects
            // (one-level search is good enough)
            // setup the directory searcher object
            string filter = "(" + PropertyManager.ObjectCategory + "=site)";
            string[] propertiesToLoad = new string[1];

            propertiesToLoad[0] = PropertyManager.Cn;

            ADSearcher searcher = new ADSearcher(sitesEntry, filter, propertiesToLoad, SearchScope.OneLevel);
            SearchResultCollection? resCol = null;

            try
            {
                resCol = searcher.FindAll();

                foreach (SearchResult res in resCol)
                {
                    // an existing site
                    sites.Add(new ActiveDirectorySite(_context, (string)PropertyManager.GetSearchResultPropertyValue(res, PropertyManager.Cn)!, true));
                }
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(_context, e);
            }
            finally
            {
                // call dispose on search result collection
                resCol?.Dispose();
            }
            return sites;
        }

        private ArrayList GetApplicationPartitions()
        {
            ArrayList appNCs = new ArrayList();
            DirectoryEntry rootDSE = _directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.RootDSE);
            DirectoryEntry partitionsEntry = _directoryEntryMgr.GetCachedDirectoryEntry(WellKnownDN.PartitionsContainer);

            // search for all the "crossRef" objects that have the
            // ADS_SYSTEMFLAG_CR_NTDS_NC set and the SYSTEMFLAG_CR_NTDS_DOMAIN flag not set
            // (one-level search is good enough)
            // setup the directory searcher object

            // build the filter
            StringBuilder str = new StringBuilder(100);
            str.Append("(&(");
            str.Append(PropertyManager.ObjectCategory);
            str.Append("=crossRef)(");
            str.Append(PropertyManager.SystemFlags);
            str.Append(":1.2.840.113556.1.4.804:=");
            str.Append((int)SystemFlag.SystemFlagNtdsNC);
            str.Append(")(!(");
            str.Append(PropertyManager.SystemFlags);
            str.Append(":1.2.840.113556.1.4.803:=");
            str.Append((int)SystemFlag.SystemFlagNtdsDomain);
            str.Append(")))");

            string filter = str.ToString();
            string[] propertiesToLoad = new string[2];
            propertiesToLoad[0] = PropertyManager.NCName;
            propertiesToLoad[1] = PropertyManager.MsDSNCReplicaLocations;

            ADSearcher searcher = new ADSearcher(partitionsEntry, filter, propertiesToLoad, SearchScope.OneLevel);
            SearchResultCollection? resCol = null;

            try
            {
                resCol = searcher.FindAll();

                string? schemaNamingContext = (string?)PropertyManager.GetPropertyValue(_context, rootDSE, PropertyManager.SchemaNamingContext);
                string? configurationNamingContext = (string?)PropertyManager.GetPropertyValue(_context, rootDSE, PropertyManager.ConfigurationNamingContext);

                foreach (SearchResult res in resCol)
                {
                    // add the name of the appNC only if it is not
                    // the Schema or Configuration partition
                    string nCName = (string)PropertyManager.GetSearchResultPropertyValue(res, PropertyManager.NCName)!;

                    if ((!(nCName.Equals(schemaNamingContext))) && (!(nCName.Equals(configurationNamingContext))))
                    {
                        ResultPropertyValueCollection replicaLocations = res.Properties[PropertyManager.MsDSNCReplicaLocations];
                        if (replicaLocations.Count > 0)
                        {
                            string replicaName = Utils.GetAdamDnsHostNameFromNTDSA(_context, (string)replicaLocations[Utils.GetRandomIndex(replicaLocations.Count)]!);
                            DirectoryContext appNCContext = Utils.GetNewDirectoryContext(replicaName, DirectoryContextType.DirectoryServer, _context);
                            appNCs.Add(new ApplicationPartition(appNCContext, nCName, null, ApplicationPartitionType.ADAMApplicationPartition, new DirectoryEntryManager(appNCContext)));
                        }
                    }
                }
            }
            catch (COMException e)
            {
                throw ExceptionHelper.GetExceptionFromCOMException(_context, e);
            }
            finally
            {
                // call dispose on search result collection
                resCol?.Dispose();
            }
            return appNCs;
        }

        private void CheckIfDisposed()
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
        }
        #endregion private methods
    }
}