I am trying to find a txt files over computers of a domain network.
What I have done till now:
I have the list of all computers of the domain in an array.So I am iterating each computer with its corresponding address with the help of getfile command.
Where I am stuck :
There are some computers over which I don't have access.So my search is either taking a long time to leap those exceptions or it gets struck at some point.As there are more that 500 systems so I want to increase the speed and accuracy of my program.
I am mostly getting network not found error.
Here is my code:
namespace ABC
{
class Program
{
static void Main(string[] args)
{
List<string> cnames=new List<string>();
DirectoryEntry entry = new DirectoryEntry("LDAP://abc.com", "username", "password", AuthenticationTypes.Secure);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = ("(objectClass=computer)");
foreach (SearchResult resEnt in mySearcher.FindAll())
{
string name = resEnt.GetDirectoryEntry().Name;
if (name.StartsWith("CN="))
name = name.Remove(0, "CN=".Length);
cnames.Add(name);
}
int cnumbers=cnames.Count;
for (int i = 0; i < cnumbers;i++ )
{
string s = "\\\\" + cnames[i] + "\\ab\\cd";
string[] dirs = null;
Console.WriteLine("Name of Computer=" + cnames[i]);
try
{
dirs = Directory.GetFiles(s);
try
{
foreach (string dir in dirs)
{
Console.WriteLine(dir);
}
}
catch (Exception e)
{
}
}
catch (Exception)
{
}
}
}
}
}
Related
Update:
For me, LDAP way only worked for finding email addresses inside AD groups, for eg: named ITSolutionDeliveryDevelopers group. NOT inside Exchange Distribution Lists, for eg: named abc#domainname.com.
// I was able to figure out entry as suggested by #Gabriel Luci and
// all of the following possible formats worked for me:
// ngroupnet.com is my company domain name.
var entry = new DirectoryEntry();
var entry = new DirectoryEntry("LDAP://ngroupnet.com");
var entry = new DirectoryEntry("LDAP://ngroupnet.com", "MyAccountUsername", "MyAccountPassword");
var entry = new DirectoryEntry("LDAP://ngroupnet.com", "MyName#mycompany.com", "MyEmailAccountPassword");
For my complete answer, take a look below: https://stackoverflow.com/a/71518937/8644294
Original Question:
What is the best way to get all the individual email addresses comprising an exchange distribution list?
For eg: I have this distribution list called abc#domainname.com that has email addresses:
a#domainname.com
b#domainname.com
c#domainname.com
Now I need to get these addresses using C# code.
I found solution using LDAP but I felt it'd be a hassle to figure out LDAP path to my Active Directory.
// How do I get this LDAP Path, username and password?
// Is the username and password service account credentials of the app?
// And do they need to be registered in AD?
var entry = new DirectoryEntry("LDAP Path");//, username, password);
LDAP Way:
public static List<string> GetDistributionListMembers(string dlName = "abc#domainname.com")
{
var result = new List<string>();
try
{
// How do I get this LDAP Path?
var entry = new DirectoryEntry("LDAP Path");//, username, password);
var search = new DirectorySearcher(entry);
search.Filter = $"CN={dlName}";
int i = search.Filter.Length;
string str = "", str1 = "";
foreach (SearchResult AdObj in search.FindAll())
{
foreach (String objName in AdObj.GetDirectoryEntry().Properties["member"])
{
str += Convert.ToString(objName) + "<Br>";
int selIndex = objName.IndexOf("CN=") + 3;
int selEnd = objName.IndexOf(",OU") - 3;
str1 += objName.Substring(selIndex, selEnd).Replace("\\", "");
DirectorySearcher dsSearch = new DirectorySearcher(entry);
dsSearch.Filter = "CN=" + objName.Substring(selIndex, selEnd).Replace("\\", "");
foreach (SearchResult rs in dsSearch.FindAll())
{
//str1 += "<p align='right'><font face='calibri' color='#2266aa' size=2>" + Convert.ToString(rs.GetDirectoryEntry().Properties["mail"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["displayName"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["sAMAccountName"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["department"].Value) + "|" + Convert.ToString(rs.GetDirectoryEntry().Properties["memberOf"].Value) + "</font></p>";
str1 = Convert.ToString(rs.GetDirectoryEntry().Properties["mail"].Value);
result.Add(str1);
}
}
}
return result;
}
catch (Exception ex)
{
//Do some logging or what have you.
throw;
}
}
So I just went with the EWS route.
EWS Way:
public static static List<string> GetDistributionListMembers(string dlName = "abc#domainname.com")
{
try
{
var service = new ExchangeService();
var cred = new WebCredentials("sharedmailbox#domain.com", "some_password");
service.Credentials = cred;
service.Url = new Uri("https://outlook.office365.com/ews/exchange.asmx");
service.TraceEnabled = true;
service.TraceFlags = TraceFlags.All;
var expandedEmailAddresses = new List<string>();
ExpandGroupResults myGroupMembers = service.ExpandGroup(dlName);
foreach (EmailAddress address in myGroupMembers.Members)
{
expandedEmailAddresses.Add(address.Address);
}
return expandedEmailAddresses;
}
catch (Exception ex)
{
// The DL doesn't have any members. Handle it how you want.
// Handle/ Log other errors.
}
}
Is EWS approach a good way?
If Yes, then I'm good. If not, I'll have to figure out that LDAP path.
Or if there's even a better way, please let me know.
If the computer you run this from is joined to the same domain as the group you're looking for, then you don't need to figure out the LDAP path. You can just do:
var search = new DirectorySearcher();
If your computer is not joined to the same domain, then you just use the domain name:
var entry = new DirectoryEntry("LDAP://domainname.com");
This requires that there is no firewall blocking port 389 between your computer and the domain controller(s). If you need to pass credentials, then do that:
var entry = new DirectoryEntry("LDAP://domainname.com", username, password);
The credentials can be any user on the domain.
That said, there are a lot of inefficiencies in your code that will make it run much slower than needed. I wrote an article about this that can help you update your code: Active Directory: Better Performance
Is EWS approach a good way?
If it works, it works. I'm not an expert on EWS (although I have used it), but I'm fairly certain that's using Basic Authentication, which is going to be disabled in October.
If all the Mailboxes are on Office365 then i would suggest you use the Graph API instead eg https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=http . There are several advantage in terms of security eg you could use Application permissions and all you need is access to the directory while if you did the same thing in EWS it would require full access to at least one mailbox.
LDAP will be best performing of the 3 if ultimate speed in the only thing that is important.
My complete solution for future reference. :)
EWS Way - For expanding Exchange Distribution Lists
public class SomeHelper
{
private static ExchangeService _exchangeService = null;
public static async Task<HashSet<string>> GetExchangeDistributionListMembersAsync(IEnumerable<string> dlNames)
{
var allEmailAddresses = new HashSet<string>();
foreach (var dlName in dlNames)
{
if (!SomeCache.TryGetCachedItem(dlName, out var dlMembers))
{
var groupEmailAddresses = new List<string>();
var exchangeService = await GetExchangeServiceAsync();
try
{
var myGroupMembers = exchangeService.ExpandGroup(dlName);
// Add the group members.
foreach (var address in myGroupMembers.Members)
{
groupEmailAddresses.Add(address.Address);
}
}
catch (Exception ex)
{
//If it can't expand the dlName, just return it.
groupEmailAddresses.Add(dlName);
//groupEmailAddresses.Add($"Attempting to expand '{dlName}' resulted in error message: '{ex.Message}'.");
}
// Cache the groupEmailAddresses for 7 days.
// Because Distribution Lists rarely change and expanding DL is an expensive operation.- AshishK Notes
SomeCache.AddItemToCache(dlName, groupEmailAddresses, 10080);
allEmailAddresses.UnionWith(groupEmailAddresses);
}
else
{
allEmailAddresses.UnionWith((List<string>)dlMembers);
}
}
return allEmailAddresses;
}
private static async Task<ExchangeService> GetExchangeServiceAsync()
{
if (_exchangeService == null)
{
_exchangeService = new ExchangeService();
var exchangeUrl = "https://outlook.office365.com/ews/exchange.asmx";
var cred = new WebCredentials("sharedmailbox#domain.com", "some_password");
_exchangeService.Credentials = cred;
//_exchangeService.AutodiscoverUrl("sharedmailbox#domain.com");
_exchangeService.Url = new Uri(exchangeUrl);
_exchangeService.TraceEnabled = true;
_exchangeService.TraceFlags = TraceFlags.All;
return _exchangeService;
}
else
{
return _exchangeService;
}
}
}
public class SomeCache
{
private static readonly ObjectCache _cache = MemoryCache.Default;
public static void AddItemToCache(string key, object itemToAdd, int cacheDurationMinutes)
{
var _policy = new CacheItemPolicy
{
Priority = CacheItemPriority.Default,
AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(cacheDurationMinutes)
};
_cache.Set(key, itemToAdd, _policy);
}
public static bool TryGetCachedItem(string key, out object cachedObject)
{
try
{
cachedObject = _cache[key] as object;
}
catch (Exception ex)
{
cachedObject = null;
}
return !(cachedObject == null);
}
}
LDAP Way - For expanding Active Directory Groups
public static List<string> GetADGroupDistributionListMembers(string adGroupName)
{
var returnResult = new List<string>();
var entry = GetDirectoryEntry();
DirectorySearcher groupSearch = new DirectorySearcher(entry)
{
Filter = "(SAMAccountName=" + adGroupName + ")"
};
groupSearch.PropertiesToLoad.Add("member");
SearchResult groupResult = groupSearch.FindOne(); // getting members who belong to the adGroupName
if (groupResult != null)
{
for (int iSearchLoop = 0; iSearchLoop < groupResult.Properties["member"].Count; iSearchLoop++)
{
string userName = groupResult.Properties["member"][iSearchLoop].ToString();
int index = userName.IndexOf(',');
userName = userName.Substring(0, index).Replace("CN=", "").ToString(); // the name of the user will be fetched.
DirectorySearcher search = new DirectorySearcher(entry)
{
Filter = "(name=" + userName + ")"
};
search.PropertiesToLoad.Add("mail");
SearchResult result = search.FindOne(); //finding the mail id
if (result != null)
{
returnResult.Add(result.Properties["mail"][0].ToString());
}
}
}
return returnResult;
}
public static DirectoryEntry GetDirectoryEntry()
{
DirectoryEntry entryRoot = new DirectoryEntry("LDAP://RootDSE");
string Domain = (string)entryRoot.Properties["defaultNamingContext"][0];
DirectoryEntry de = new DirectoryEntry
{
Path = "LDAP://" + Domain,
AuthenticationType = AuthenticationTypes.Secure
};
return de;
}
I'm writing a code to figure out the missing data and where i am having a mismatch or where it is not present.
There are 3 scenarios.
Images present as per db and in server (positive)
Images present in DB but not in Server.(Negative)
Images present in server but not in DB (Negative)
I have written a code using else if and i am able to capture only two scenario
Positive
Negative
How do i classify the two negative scenarios
(ie)Images present in DB but not in Server(Negative) and Images present in server but not in DB (Negative) to this code
namespace DataTableColumnCommaSeperated
{
class Program
{
static void Main(string[] args)
{
fileOperation();
}
private static void fileOperation()
{
try
{
string main_directory = #"D:\DMSArchivalPhase1\";//src_directory location
string[] lead_list = File.ReadAllLines(main_directory + #"list.txt");//first input
string[] image_list = File.ReadAllLines(main_directory + #"image_list.txt");//image name as per DB
foreach(string lead_id in lead_list)
{
Console.WriteLine(lead_id);
string[] src_list = File.ReadAllLines(main_directory + #"src_list.txt");
foreach (string src_dir in src_list)
{
string final_path = src_dir + lead_id;
if (Directory.Exists(final_path))
{
var filePaths = Directory.GetFiles(final_path, "*.*", SearchOption.AllDirectories).Where(name => !name.EndsWith(".gz"));//images present as per server
foreach (string path in filePaths)
{
FileInfo f = new FileInfo(path);
string strNewName = Path.GetFileNameWithoutExtension(f.Name);
if (Array.Exists(image_list, element => element == strNewName))
{
Console.WriteLine("File exist");
}
else if (Array.Exists(image_list, element => element != strNewName))
{
Console.WriteLine(strNewName + "File name doesnot exist in DMS/Databse");
}
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
I have a master account file having more than 300000 records. This file is having Office, FA, UUID fields along with other fields. We have a webservice which retuns currently enrolled offices for pilot.
I need to generate a output file with unique UUIDs from master account file which are part of pilot. The below is my code.
public class Job
{
static Dictionary<string, string> UUIDOfficeFA = new Dictionary<string, string>();
static Dictionary<string, string> UUIDs = new Dictionary<string, string>();
static string PilotOfficeList = string.Empty;
/// <summary>
///
/// </summary>
public void RunJob()
{
try
{
DirectoryInfo directoryInfo = new DirectoryInfo(ConfigurationSettings.AppSettings["AccountFileFolder"]);
if (directoryInfo != null || directoryInfo.Exists == false)
{
FileInfo[] files = directoryInfo.GetFiles();
DateTime lastWrite = DateTime.MinValue;
FileInfo lastWritenFile = null;
foreach (FileInfo file in files)
{
if (file.LastWriteTime > lastWrite)
{
lastWrite = file.LastWriteTime;
lastWritenFile = file;
}
}
if (lastWritenFile != null)
{
if (RetrieveUUIDs(lastWritenFile))
{
WriteFile();
}
}
else
{
throw new Exception("No matching account file found for processing");
}
}
}
catch (Exception ex)
{
throw ex;
}
}
static void WriteFile()
{
try
{
string FileName = "Testfile_" + System.DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt";
string Path = ConfigurationSettings.AppSettings["TestFolder"] + #"\" + FileName;
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<string, string> kvp in UUIDs)
{
sb.AppendLine(kvp.Value);
}
using (StreamWriter writer = File.AppendText(Path))
{
writer.Write(sb.ToString());
writer.Flush();
}
}
catch (Exception ex)
{
throw ex;
}
}
static bool RetrieveUUIDs(FileInfo file)
{
try
{
using (StreamReader sr = file.OpenText())
{
string accountFileData = null;
accountFileData = sr.ReadToEnd();
string[] str = accountFileData.Trim().Split(new string[] { "\n" }, StringSplitOptions.None);
List<string> AccountInfo = new List<string>(str);
if (AccountInfo.Count > 0)
{
TestWebservice client = new TestWebservice();
GetBrancheListRequest request = new GetBrancheListRequest();
GetBrancheListResponse response =client.GetBrancheList(request);
if (response != null)
{
PilotOfficeList = response.GetBrancheListResult;
if (string.IsNullOrEmpty(PilotOfficeList))
{
throw new Exception("No branch is enrolled for pilot");
}
}
}
foreach (string accountInfo in AccountInfo)
{
RetrieveUUID(accountInfo);
}
}
}
catch (Exception ex)
{
throw ex;
}
return true;
}
private static void RetrieveUUID(string line)
{
try
{
string UUID = line.Substring(2, 50).Trim();
string Office = line.Substring(444, 3).Trim();
string FA = line.Substring(447, 3).Trim();
if (!string.IsNullOrEmpty(PilotOfficeList))
{
if (!string.IsNullOrEmpty(UUID) || !string.IsNullOrEmpty(Office))
{
if (PilotOfficeList.IndexOf(Office) > -1)
{
if (!UUIDOfficeFA.ContainsKey(UUID + Office + FA))
{
UUIDOfficeFA.Add(UUID + Office + FA, UUID + Office + FA);
if (!UUIDs.ContainsKey(UUID))
{
UUIDs.Add(UUID, UUID);
}
}
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
}
The issue is when this program runs the CPU utilisation goes to 100%. Although entire job completes within 2 minutes but i can't move this on server beacuse it might cause issue for other apps. Please suggest how i can optimize this so that CPU doesn't goes to 100%.
Thank you so much for your help in prior.
Note- It appears that following are few major CPU utilization points
1-
accountFileData = sr.ReadToEnd();
2-
string[] str = accountFileData.Trim().Split(new string[] { "\n" }, StringSplitOptions.None);
3-
foreach (string accountInfo in AccountInfo)
{
RetrieveUUID(accountInfo);
}
First of all, please forgive me if I'm not using the correct terminologies. Correct me wherever I'm using the wrong terminology.
The objective is to programmatically retrieve the lastLogon date of a given username.
We have what I believe is a forest; two AD servers like - adserver01.aa.mycompany.com and adserver02.aa.mycompany.com
I connected to these servers from a third machine using Microsoft's ADExplorer to inspect the objects. There I see some users having lastLogon date available in adserver01, but not in adserver02. For example, the value for lastLogon is 0x0 in adserver02 whereas it is a valid date in adserver01 for some users.
The code I've developed so far as a Windows Forms application, works fine if only one AD Server is involved. How do I check both servers and return the non-zero value if available, in either for lastLogon date attribute?
private static string GetLastActivityDate(string UserName)
{
string domainAndUserName = String.Format(#"LDAP://aa.mycompany.com/CN={0},OU=CLIENT_PROD,OU=clients.mycompany.com,DC=aa,DC=mycompany,DC=com", UserName);
string OUAdminUserName = "abc";
string OUAdminPassword = "xyz";
AuthenticationTypes at = AuthenticationTypes.Secure;
DateTime lastActivityDate;
string returnvalue;
long lastLogonDateAsLong;
using (DirectoryEntry entryUser = new DirectoryEntry(domainAndUserName, OUAdminUserName, OUAdminPassword, at))
using (DirectorySearcher mysearcher = new DirectorySearcher(entryUser))
try
{
using (SearchResultCollection results = mysearcher.FindAll())
{
if (results.Count >= 1)
{
DirectoryEntry de = results[0].GetDirectoryEntry();
lastLogonDateAsLong = GetInt64(de, "lastLogon");
try
{
if (lastLogonDateAsLong != -1)
{
lastActivityDate = DateTime.FromFileTime(lastLogonDateAsLong);
returnvalue = lastActivityDate.ToString();
}
else
{
returnvalue = "-Not available-";
}
}
catch (System.ArgumentOutOfRangeException aore)
{
returnvalue = "Not available";
}
}
else
{
returnvalue = string.Empty;
}
}
}
catch (System.DirectoryServices.DirectoryServicesCOMException dsce)
{
returnvalue = "- Not available -";
}
return returnvalue;
}
Thank you.
EDIT:
private static Int64 GetInt64(DirectoryEntry entry, string attr)
{
DirectorySearcher ds = new DirectorySearcher(
entry,
String.Format("({0}=*)", attr),
new string[] { attr },
SearchScope.Base
);
SearchResult sr = ds.FindOne();
if (sr != null)
{
if (sr.Properties.Contains(attr))
{
return (Int64)sr.Properties[attr][0];
}
}
return -1;
}
Forgot to mention, the AD schema, structure etc, looks exactly alike in the two servers.
Check this post
http://www.codeproject.com/Articles/19181/Find-LastLogon-Across-All-Windows-Domain-Controlle
I had the same issue but but only for one domain,
I solved it by using the following code however i'm checking the lastLogin of all users
public static Dictionary<string, DateTime> UsersLastLogOnDate()
{
var lastLogins = new Dictionary<string, DateTime>();
DomainControllerCollection domains = Domain.GetCurrentDomain().DomainControllers;
foreach (DomainController controller in domains)
{
try
{
using (var directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}", controller.Name)))
{
using (var searcher = new DirectorySearcher(directoryEntry))
{
searcher.PageSize = 1000;
searcher.Filter = "(&(objectClass=user)(!objectClass=computer))";
searcher.PropertiesToLoad.AddRange(new[] { "distinguishedName", "lastLogon" });
foreach (SearchResult searchResult in searcher.FindAll())
{
if (searchResult.Properties.Contains("lastLogon"))
{
var lastLogOn = DateTime.FromFileTime((long)searchResult.Properties["lastLogon"][0]);
var username = Parser.ParseLdapAttrValue(searchResult.Properties["distinguishedName"][0].ToString());
if (lastLogins.ContainsKey(username))
{
if (DateTime.Compare(lastLogOn, lastLogins[username]) > 0)
{
lastLogins[username] = lastLogOn;
}
}
else
{
lastLogins.Add(username, lastLogOn);
}
}
}
}
}
}
catch (System.Runtime.InteropServices.COMException comException)
{
// Domain controller is down or not responding
Log.DebugFormat("Domain controller {0} is not responding.",controller.Name);
Log.Error("Error in one of the domain controllers.", comException);
continue;
}
}
return lastLogins;
}
On top of the code you can use the following to get all domains in a forest.
Forest currentForest = Forest.GetCurrentForest();
DomainCollection domains = currentForest.Domains;
foreach(Domain domain in domains)
{
// check code above
}
There may be a simpler approach? There is actually another attribute lastLogonTimestamp, added with 2003 domain level I think, that tries to keep a single, consistent value across the domain for the last login. Alas, it has a bizarre replication time pattern, and could be up to two weeks out of date.
In order to determine if computer accounts are orphaned, I would like to query all domain controllers of all trusted domains to retrieve the lastLogon and lastLogonTimeStamp for all computers. I have a program that works (at least in my test environment), but I have a few questions, that I hope you might be able to answer.
Are the methods I’m using to find the domains and domain controllers and then retrieve the AD information, using the least amount of resources (network / domain controller CPU and RAM) as possible? How could they be improved?
Is it possible to have more than 1 value in a dictionary key / value pair?
Having a dictionary for LastLogIn and a different one for LastLogInTimestamp seems a waste.
Referring to the dictionary and the AD properties: How can I check for non-existent values as opposed to using Try / Catch?
try
{ // Is this DC more current than the last?
if (dict_LastLogIn[pc] < (long)result.Properties["lastlogon"][0])
{
dict_LastLogIn[pc] = (long)result.Properties["lastlogon"][0];
}
}
catch
{ // The item doesn't exist yet..
try
{
dict_LastLogIn[pc] = (long)result.Properties["lastlogon"][0];
}
catch
{ // .. or
// There is no last LastLogin...
dict_LastLogIn[pc] = 0;
}
}
Here the entire code:
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
namespace dictionary
{
class Program
{
internal static Dictionary<string, long> dict_LastLogIn =
new Dictionary<string, long>();
internal static Dictionary<string, long> dict_LastLogInTimeStamp =
new Dictionary<string, long>();
internal static Dictionary<string, DateTime> output =
new Dictionary<string, DateTime>();
internal static bool AreAllDCsResponding = true;
static void Main(string[] args)
{
Console.BufferWidth = 150;
Console.BufferHeight = 9999;
Console.WindowWidth = 150;
Dictionary<String, int> dict_domainList = new Dictionary<String, int>();
Dictionary<String, int> dict_dcList = new Dictionary<String, int>();
//Get the current domain's trusts.
Domain currentDomain = Domain.GetCurrentDomain();
Console.WriteLine("Retrieved the current Domain as {0}", currentDomain.ToString());
var domainTrusts = currentDomain.GetAllTrustRelationships();
Console.WriteLine(" {0} trusts were found.", domainTrusts.Count);
//Add the current domain to the dictonary. It won't be in domainTrusts!
dict_domainList.Add(currentDomain.ToString(), 0);
// Then add the other domains to the dictonary...
foreach (TrustRelationshipInformation trust in domainTrusts)
{
dict_domainList.Add(trust.TargetName.Substring(0, trust.TargetName.IndexOf(".")).ToUpper(), 0);
Console.WriteLine(" Adding {0} to the list of trusts.", trust.TargetName.Substring(0, trust.TargetName.IndexOf(".")).ToUpper());
}
// Now get all DCs per domain
foreach (var pair in dict_domainList)
{
DirectoryContext dc = new DirectoryContext(DirectoryContextType.Domain, pair.Key);
Domain _Domain = Domain.GetDomain(dc);
foreach (DomainController Server in _Domain.DomainControllers)
{
dict_dcList.Add(Server.Name, 0);
Console.WriteLine(" Adding {0} to the list of DCs.", Server.Name.ToUpper());
}
// Now search through every DC
foreach (var _pair in dict_dcList)
{
Console.WriteLine(" Querying {0} for Computer objects.", _pair.Key.ToUpper());
Search(pair.Key);
Console.WriteLine("\n");
Console.WriteLine("The following Computer objects were found:");
}
if (AreAllDCsResponding == true)
{
ConvertTimeStamp(dict_LastLogIn);
}
else
{
ConvertTimeStamp(dict_LastLogInTimeStamp);
}
Console.ReadLine();
}
}
internal static void Search(string domainName)
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + domainName);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = ("(&(ObjectCategory=computer))");//(lastlogon=*)(lastlogonTimeStamp=*))");
mySearcher.SizeLimit = int.MaxValue;
mySearcher.PropertiesToLoad.Add("DistinguishedName");
mySearcher.PropertiesToLoad.Add("lastlogon");
mySearcher.PropertiesToLoad.Add("lastlogonTimeStamp");
try
{
foreach (System.DirectoryServices.SearchResult result in mySearcher.FindAll())
{
string pc = result.Properties["DistinguishedName"][0].ToString();
try
{ // Is this DC more current than the last?
if (dict_LastLogIn[pc] < (long)result.Properties["lastlogon"][0])
{
dict_LastLogIn[pc] = (long)result.Properties["lastlogon"][0];
}
}
catch
{ // The item doesn't exist yet..
try
{
dict_LastLogIn[pc] = (long)result.Properties["lastlogon"][0];
}
catch
{ // .. or
// There is no last LastLogin...
dict_LastLogIn[pc] = 0;
}
}
try
{
// Not yet replicated?...
if (dict_LastLogInTimeStamp[pc] < (long)result.Properties["lastlogonTimeStamp"][0])
{
dict_LastLogInTimeStamp[pc] = (long)result.Properties["lastlogonTimeStamp"][0];
}
}
catch
{ // The item doesn't exist yet..
try
{
dict_LastLogInTimeStamp[pc] = (long)result.Properties["lastlogonTimeStamp"][0];
}
catch
{ // .. or
// There is no last LastLoginTimeStamp...
dict_LastLogInTimeStamp[pc] = 0;
}
}
}
}
catch (System.Runtime.InteropServices.COMException)
{
//If even one DC doesn't answer, don't use LastLogon!
//Use the less accurate, but replicated(!) LastLogonTimeStamp.
AreAllDCsResponding = false;
}
}
internal static void ConvertTimeStamp(Dictionary<string, long> _dict)
{
foreach (var pair in _dict)
{
output.Add(pair.Key, DateTime.FromFileTime(pair.Value));
Console.WriteLine("{0} - {1}", pair.Key, DateTime.FromFileTime(pair.Value));
}
}
}
}
Thanks for any help you can offer.
At a high level, I'm not sure what your end-game is with this, but, are you sure you really want to query every single DC for lastLogon? This could be really expensive. Also, why are you walking trust chains? Are you sure you don't just want all the domains in a given Forest (Forest.Domains)?
In answer to your questions:
This looks OK perf-wise, however you should do a couple things:
Tweak your filter to (&(objectCategory=computer)(objectClass=computer))
Add mySearcher.PageSize = 1000
Remove mySearcher.SizeLimit = int.MaxValue
You could use a Tuple - http://msdn.microsoft.com/en-us/library/system.tuple(VS.90).aspx. Or just define a custom class as your value and then declare Dictionary as your dictionary:
public class LogonTimeStamps
{
public long LastLogon { get; set; }
public long LastLogonTimeStamp { get; set; }
}
For the Dictionary, use myDictionary.ContainsKey(yourKey). For AD, you should be able to use result.Properties.Contains("yourAttribute").