0

My specific Question: How to I narrow down my search for active directory accounts that DO NOT have employeeNumber attribute set (is not null or empty)?

My work around is to go over the results and check the employeeNumber and removing those accounts. However, I would like my query to narrow down the results before I have to filter then manually.

The line that I think is not even firing a filter : ((DirectorySearcher)ps.GetUnderlyingSearcher()).Filter = "(&(objectCategory=Person)(objectClass=User)(!employeeNumber=*))";// I would like for it to return only Ad Accounts that have an employeeNumber set

 PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, "myDomain");
                UserPrincipal user = new UserPrincipal(domainContext);
                user.SamAccountName = ParamSamAccountName;
                user.Enabled = true;//only enabled users
                user.PasswordNeverExpires = false; //this should get rid of service accounts

                PrincipalSearcher pS = new PrincipalSearcher();
                pS.QueryFilter = user;

                PrincipalSearcher ps = new PrincipalSearcher(user);
                ((DirectorySearcher)ps.GetUnderlyingSearcher()).PageSize = 500;
               ((DirectorySearcher)ps.GetUnderlyingSearcher()).Filter = "(&(objectCategory=Person)(objectClass=User)(!(employeeNumber=*)))";//this doesnt seem to be working... bug...
                var searchResults = SafeFindAll(ps);



      private static IEnumerable<Principal> SafeFindAll(PrincipalSearcher searcher)
            {
                using (var results = searcher.FindAll())
                {
                    foreach (var result in results)
                    {
                        yield return result;
                    }
                } // SearchResultCollection will be disposed here
            }
hidden
  • 3,216
  • 8
  • 47
  • 69
  • The answer to this [question](http://stackoverflow.com/questions/8051470/get-active-directory-users-whose-firstname-in-active-directory-is-not-empty-or-n?rq=1) makes it seem like you are following the right approach. Have you tried using the MMC plug-in for AD to verify your query? – Sven Grosen Jan 14 '15 at 20:08
  • You want to find all users that have employee number or not have employee number? The search filter in the code is different from the one you pasted above the code. – baldpate Jan 15 '15 at 06:55
  • 1
    i want to find everyone that does not have employeeNumber attribute set. – hidden Jan 15 '15 at 07:57
  • The search filter seems okay... Another guess is when you run the above code you are using the GC or LDAP port? employeeNumber attribute is not on GC, so the filter `!(employeeNumber=*)` is always true on GC. – baldpate Jan 16 '15 at 05:59

3 Answers3

3

Here's my approach:

1.Subclass UserPrincipal to introduce noEmployeeNumber and objectCategory properties (here is very good example which helped me a lot):

[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalEx : UserPrincipal
{
    // Implement the constructor using the base class constructor. 
    public UserPrincipalEx(PrincipalContext context) : base(context) { }

    // Implement the constructor with initialization parameters.    
    public UserPrincipalEx(PrincipalContext context,
                         string samAccountName,
                         string password,
                         bool enabled)
        : base(context, samAccountName, password, enabled)
    { }

    // Create the "employeeNumber" property.    
    [DirectoryProperty("!employeeNumber")]
    public bool noEmployeeNumber
    {
        get
        {
            if (ExtensionGet("!employeeNumber").Length != 1) return false;
            string empNum = (string)ExtensionGet("!employeeNumber")[0];
            if (empNum == "*") return true; else return false;
        }
        set 
        {
            ExtensionSet("!employeeNumber", "*"); 
        }
    }
    // Create the "objectCategory" property.    
    [DirectoryProperty("objectCategory")]
    public string objectCategory
    {
        get
        {
            object[] result = this.ExtensionGet("objectCategory");
            if (result != null)
            {
                return (string)result[0];
            }
            else
            {
                return string.Empty;
            }
        }
        set { this.ExtensionSet("objectCategory", value); }
    }

    // Implement the overloaded search method FindByIdentity.
    public static new UserPrincipalEx FindByIdentity(PrincipalContext context, string identityValue)
    {
        return (UserPrincipalEx)FindByIdentityWithType(context, typeof(UserPrincipalEx), identityValue);
    }

    // Implement the overloaded search method FindByIdentity. 
    public static new UserPrincipalEx FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
    {
        return (UserPrincipalEx)FindByIdentityWithType(context, typeof(UserPrincipalEx), identityType, identityValue);
    }
}

2.Change the code to use new UserPrincipalEx and assign the values to it's new properties:

PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, "mydomain");
UserPrincipalEx user = new UserPrincipalEx(domainContext);

// here are our new properties:
user.noEmployeeNumber = true;
user.objectCategory = "person";

user.Enabled = true; //only enabled users
user.PasswordNeverExpires = false; //this should get rid of service accounts
PrincipalSearcher ps = new PrincipalSearcher(user);
var searchResults = ps.FindAll();                 

The result will be list of all users without employeeNumber property set.

Eugene
  • 179
  • 2
  • 7
1

Your question is a bit confusing. If you want WITHOUT employeeNumber set, then you are correct, if you want WITH employeeNumber set then you need this: (&(objectCategory=Person)(objectClass=User)(employeeNumber=*))

Additionally you need to make sure you get the LDAP connection. Some code below that might be helpful, also see this blog: http://www.codeproject.com/Articles/18102/Howto-Almost-Everything-In-Active-Directory-via-C#20

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LDAPCSharp
{

    using System.DirectoryServices;
    using System.DirectoryServices.ActiveDirectory;

    class Program
    {
        static void Main(string[] args)
        {
            var ldapDomain = FriendlyDomainToLdapDomain("domainRemoved");


            var allResults = FindAllWithEmployeeNumber(ldapDomain);

            foreach (var searchResult in allResults)
            {
                using (var entry = searchResult.GetDirectoryEntry())
                {
                    foreach (var value in entry.Properties.PropertyNames)
                    {
                        Console.WriteLine(value);
                    }
                }
            }
        }

        /// <summary>
        /// The find all.
        /// </summary>
        /// <param name="ldapDomain">
        /// The ldap domain.
        /// </param>
        /// <returns>
        /// The <see cref="IEnumerable"/>.
        /// </returns>
        public static IEnumerable<SearchResult> FindAllWithEmployeeNumber(string ldapDomain)
        {
            string connectionPrefix = "LDAP://" + ldapDomain;
            DirectoryEntry entry = new DirectoryEntry(connectionPrefix);
            DirectorySearcher mySearcher = new DirectorySearcher(entry);

            // all that have employeenumber set
            mySearcher.Filter = "(&(objectCategory=Person)(objectClass=User)(employeeNumber=*))";

            // all WITHOUT employeenumber set
            // mySearcher.Filter = (&(objectCategory=Person)(objectClass=User)(!(employeeNumber=*)))";
            mySearcher.PageSize = 10;

            var results = SafeFindAll(mySearcher);

            mySearcher.Dispose();
            return results;
        }

        public static string FriendlyDomainToLdapDomain(string friendlyDomainName)
        {
            string ldapPath = null;
            try
            {
                DirectoryContext objContext = new DirectoryContext(
                    DirectoryContextType.Domain, friendlyDomainName);
                Domain objDomain = Domain.GetDomain(objContext);
                ldapPath = objDomain.Name;
            }
            catch (DirectoryServicesCOMException e)
            {
                ldapPath = e.Message.ToString();
            }
            return ldapPath;
        }

        private static IEnumerable<SearchResult> SafeFindAll(DirectorySearcher searcher)
        {
            using (var results = searcher.FindAll())
            {
                foreach (var result in results)
                {
                    yield return (SearchResult)result;
                }
            } // SearchResultCollection will be disposed here
        }
    }
}
Matt Clark
  • 1,171
  • 6
  • 12
  • I thought that key requirement of the question was the usage of the PrincipalSearcher and the search-by-example type of search instead of DirectorySearcher... – Eugene Feb 04 '15 at 17:29
  • His question did not explicitly ask that we use PrincipalSearcher, DirectorySearcher is in my experience almost always easier to use. – Matt Clark Feb 04 '15 at 18:15
  • I agree with you. It's just seemed too obvious :) – Eugene Feb 04 '15 at 18:20
-1

2 ways to find a user

var userName = Request.ServerVariables["LOGON_USER"];
var pc = new PrincipalContext(ContextType.Domain);
var userFind = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, userName);

or

string fullName = null;
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
    using (UserPrincipal user = UserPrincipal.FindByIdentity(context,"someUserName")) 
    {
        if (user != null)
        {
            fullName = user.DisplayName;
        }
    }
}
MethodMan
  • 18,625
  • 6
  • 34
  • 52