UserPrincipal.GetGroups leaking - c#

I've tracked down a leak to my group enumeration code. I've written a test routine and I'm trying to dispose of everything but still it leaks.
Does anyone see what I'm doing wrong? In my test, I call it 100 times in a row and on my small domain, the memory footprint goes from 32MB to over 150MB.
private void EnumGroups()
{
try
{
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, "domainname.com"))
{
using (PrincipalSearcher srch = new PrincipalSearcher())
{
srch.QueryFilter = new UserPrincipal(context);
// do the search
using (PrincipalSearchResult<Principal> results = srch.FindAll())
{
// enumerate over results
foreach (UserPrincipal up in results)
{
using (PrincipalSearchResult<Principal> groups = up.GetGroups())
{
foreach (GroupPrincipal gp in groups)
gp.Dispose();
}
up.Dispose();
}
}
}
}
}
catch (Exception exc)
{
Trace.WriteLine(exc);
}
}

Execute GetMembers from another temp domain:
dTaskTemporaryDomain = AppDomain.CreateDomain("BUHLO_POBEDIT_ZLO");
var currentAssembly = Assembly.GetExecutingAssembly();
AdsApiHelper = (AdsApiHelper)_adTaskTemporaryDomain.CreateInstanceAndUnwrap(currentAssembly.GetName().FullName, typeof(AdsApiHelper).ToString());
var members = AdsApiHelper.GetMembers(GroupName);
AppDomain.Unload(_adTaskTemporaryDomain)
...
[serializeble]
class AdsApiHelper {
GetMembers(String GroupName){
.....
return groupPrincipal.GetMembers().Select(m=>m.SamAccountName);
}

Related

Invalid Operation Exception Collection was modified C#

Hello i'm trying to implement Facebook friends tracker that can track who's online and who's offline using Selenium WebDriver.
Everything works fine except when a user goes offline and i remove him from the collection it throws an exception .
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Support.Events;
using OpenQA.Selenium.Support.UI;
namespace FacebookFriendTracker
{
public delegate void StatusChangedEventHandler(object source, StatusChangedEventArgs e);
public class Watcher
{
private readonly IWebDriver _driver;
private HashSet<User> _tempUserOnline = new HashSet<User>();
public event StatusChangedEventHandler StatusChanged;
private bool run;
public Watcher()
{
//UsersOnline = new HashSet<User>();
_driver = new EdgeDriver();
_driver.Navigate().GoToUrl("https://mbasic.facebook.com");
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(20));
wait.Until(ExpectedConditions.ElementExists(By.PartialLinkText("Chat")));
var element = _driver.FindElement(By.PartialLinkText("Chat"));
element.Click();
run = false;
}
public void Tracker()
{
Thread.Sleep(5000);
//_usersOnline = _driver.FindElements(By.XPath(".//a[contains(#href,'fbid')]"));
var usersOnline = new HashSet<User>();
foreach (var userOnline in _driver.FindElements(By.XPath(".//a[contains(#href,'fbid')]")))
{
var extracedAttributeValue = userOnline.GetAttribute("href");
var regex = new Regex(#"\d+");
var id = long.Parse(regex.Match(extracedAttributeValue).Value);
var fullName = userOnline.Text;
usersOnline.Add(new User() {FullName = fullName, Id = id});
}
while (true)
{
Thread.Sleep(5000);
_driver.Navigate().Refresh();
var newUsersOnline = new HashSet<User>();
foreach (var user in _driver.FindElements(By.XPath(".//a[contains(#href,'fbid')]")))
{
var attirbute = user.GetAttribute("href");
var reg = new Regex(#"\d+");
var newId = long.Parse(reg.Match(attirbute).Value);
var newFullName = user.Text;
newUsersOnline.Add(new User() { FullName = newFullName, Id = newId });
}
_tempUserOnline = usersOnline;
foreach (var usrOnline in newUsersOnline.Except(_tempUserOnline))
{
OnStatusChanged(this , new StatusChangedEventArgs() {User = usrOnline,Status = Status.Online});
_tempUserOnline.Add(usrOnline);
}
// Here it throws and exception if the the user goes offline
foreach (var usroffline in usersOnline.Except(newUsersOnline))
{
OnStatusChanged(this, new StatusChangedEventArgs() { User = usroffline, Status = Status.Offline });
_tempUserOnline.Remove(usroffline);
}
}
}
protected virtual void OnStatusChanged(object source, StatusChangedEventArgs e)
{
if (StatusChanged !=null)
OnStatusChanged(source,e);
else
{
Console.WriteLine("User: {0} is {1} ",e.User.FullName,e.Status);
}
}
}
}
You cannot modify that collection while an emueration is in progress. A common fix is to ToList() it:
foreach (var usroffline in usersOnline.Except(newUsersOnline).ToList())
{
OnStatusChanged(this, new StatusChangedEventArgs() { User = usroffline, Status = Status.Offline });
_tempUserOnline.Remove(usroffline);
}
The problem is that you can't modify a collection while you're iterating through it with a foreach loop.
Here, you are setting _tempUserOnline to point to the same object as usersOnline:
_tempUserOnline = usersOnline;
Then further down, you have this loop, which modifies _tempUserOnline, thus modifying usersOnline also since they point to the same object. Since you are looping through usersOnline (and effectively _tempUserOnline too), you are getting the error when you do _tempUserOnline.Remove(usroffline).
foreach (var usroffline in usersOnline.Except(newUsersOnline))
{
OnStatusChanged(this, new StatusChangedEventArgs() { User = usroffline, Status = Status.Offline });
_tempUserOnline.Remove(usroffline);
}
Did you intend to copy usersOnline into tempUserOnline? If so, you should use .CopyTo() or something equivalent to make an actual copy:
User[] _tempUserOnline = new User[usersOnline.Count - 1];
usersOnline.CopyTo(_tempUserOnline);

Get Principal Context object using LDAP path

I am working on a module where I need to fetch members of an Active Directory group. This functionality already exists in the project but it was built for .Net3.5. The same is not working for .Net4.5. After some googling I found that I need to use "Principal Context" object to get the Directory entry object.
The problem here is, I need to do the testing in Test AD, which is different from my production AD.
The old way I used was allowing me to specify the test AD server path,
DirectoryEntry entry = new DirectoryEntry(ADLdapPath, ADAdminUser, ADAdminPassword, AuthenticationTypes.Secure);
Can anyone please help me find a way to specify LDAP path(AD server path) while creating "Principal Context" so that I can do the testing in Test environment.
I've used the following helper (modified) which is part of my AD tool belt to create PrincipalContext for working with AD. This should get you started. Modify it to suit your needs. Hope it helps.
public class ADHelper {
public static PrincipalContext CreatePrincipalContext(string domain = null) {
string container = null;
if (IsNullOrWhiteSpace(domain)) {
domain = GetCurrentDnsSuffix();
if (domain != null && domain.EndsWith(".com", StringComparison.InvariantCultureIgnoreCase)) {
container = GetContainers(domain);
} else {
domain = null;
}
}
var hostName = GetHostName();
if (IsNullOrWhiteSpace(domain)) {
domain = hostName;
}
ContextType contextType;
if (domain.Equals(hostName, StringComparison.InvariantCultureIgnoreCase) &&
domain.Equals(Environment.MachineName, StringComparison.InvariantCultureIgnoreCase)) {
contextType = ContextType.Machine;
} else {
contextType = ContextType.Domain;
}
PrincipalContext principalContext = null;
if (contextType == ContextType.Machine) {
principalContext = new PrincipalContext(contextType, domain);
} else {
principalContext = new PrincipalContext(contextType, domain, container, Constants.LDAPUser, Constants.LDAPPassword);
}
return principalContext;
}
public static string GetCurrentDnsSuffix() {
string dnsHostName = null;
if (NetworkInterface.GetIsNetworkAvailable()) {
var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up);
foreach (var ni in nics) {
var networkConfiguration = ni.GetIPProperties();
var dnsSuffix = networkConfiguration.DnsSuffix;
if (dnsSuffix != null) {
dnsHostName = dnsSuffix;
break;
}
var address = networkConfiguration.DnsAddresses.FirstOrDefault();
if (address != null) {
try {
var dnsHost = Dns.GetHostEntry(address.ToString());
dnsHostName = dnsHost.HostName;
} catch (System.Net.Sockets.SocketException e) {
traceError(e);
} catch (Exception e) {
traceError(e);
}
}
}
}
return dnsHostName;
}
private static string GetContainers(string ADServer) {
string[] LDAPDC = ADServer.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < LDAPDC.GetUpperBound(0) + 1; i++) {
LDAPDC[i] = string.Format("DC={0}", LDAPDC[i]);
}
String ldapdomain = Join(",", LDAPDC);
return ldapdomain;
}
public static string GetHostName() {
var ipProperties = IPGlobalProperties.GetIPGlobalProperties();
return ipProperties.HostName;
}
}
I can then use it in something like this
public static List<string> GetAllUserNames(string domain = null) {
List<string> userNames = new List<string>();
using (var principalContext = createPrincipalContext(domain)) {
//Get a list of user names in MyDomain that match filter
using (UserPrincipal userPrincipal = new UserPrincipal(principalContext)) {
using (PrincipalSearcher principalSearcher = new PrincipalSearcher(userPrincipal)) {
var results = principalSearcher
.FindAll()
.Where(c =>
(c is UserPrincipal) &&
(c as UserPrincipal).Enabled.GetValueOrDefault(false) &&
!string.IsNullOrEmpty(c.DisplayName)
);
foreach (UserPrincipal p in results) {
var temp = p.StructuralObjectClass;
string value = string.Format("{0} ({1})", p.DisplayName, p.EmailAddress ?? Join("\\", p.Context.Name, p.SamAccountName));
userNames.Add(value);
}
}
}
}
return userNames;
}

Searching for lastLogon attribute of user in multiple domain servers

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.

AD Searches and Properties and dictonary usage

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").

get all domain names on network

i need to get the list of domain names on my network...
but i am only getting the domain name with which i log into...
so for example there are 2 domains "xyz" and "xyz2"
but i get only the domain with which i log into....
here is my code:
if (!IsPostBack)
{
StringCollection adDomains = this.GetDomainList();
foreach (string strDomain in adDomains)
{
DropDownList1.Items.Add(strDomain);
}
}
}
private StringCollection GetDomainList()
{
StringCollection domainList = new StringCollection();
try
{
DirectoryEntry en = new DirectoryEntry("LDAP://");
// Search for objectCategory type "Domain"
DirectorySearcher srch = new DirectorySearcher("objectCategory=Domain");
SearchResultCollection coll = srch.FindAll();
// Enumerate over each returned domain.
foreach (SearchResult rs in coll)
{
ResultPropertyCollection resultPropColl = rs.Properties;
foreach (object domainName in resultPropColl["name"])
{
domainList.Add(domainName.ToString());
}
}
}
catch (Exception ex)
{
Trace.Write(ex.Message);
}
return domainList;
}
using System.DirectoryServices.ActiveDirectory;
....
Forest currentForest = Forest.GetCurrentForest();
DomainCollection domains = currentForest.Domains;
foreach(Domain objDomain in domains)
{
System.Diagnostics.Debug.WriteLine(objDomain.Name);
}

Categories

Resources