C
C#•11mo ago
Wolfman

Unhandled Exception: System.Runtime.InteropServices.COMException:Unknown error (0x8007203b)

I am currently working on a console app that goes through active directory and exports all the computer object to a csv document. This is what I have so far however I keep getting the error noted above when I hit around 3 million computers. Any help would be great!
using System;
using System.DirectoryServices;
using System.IO;

class Program
{
static void Main()
{
string ldapPath = "";
string csvFile = "AD_Computers.csv";

using (StreamWriter writer = new StreamWriter(csvFile))
{
writer.WriteLine("Name,SerialNumber");

using (DirectoryEntry entry = new DirectoryEntry(ldapPath))
{
using (DirectorySearcher searcher = new DirectorySearcher(entry))
{
searcher.ServerTimeLimit = new TimeSpan(1,0,0);
searcher.Filter = "(objectCategory=computer)";
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("serialNumber");

foreach (SearchResult result in searcher.FindAll())
{
string name = result.Properties["name"][0].ToString();
string serialNumber = result.Properties.Contains("serialNumber") ? result.Properties["serialNumber"][0].ToString() : "";

writer.WriteLine($"{name},{serialNumber}");
}
}
}
}

Console.WriteLine($"AD computers exported to {csvFile}");
}
}
using System;
using System.DirectoryServices;
using System.IO;

class Program
{
static void Main()
{
string ldapPath = "";
string csvFile = "AD_Computers.csv";

using (StreamWriter writer = new StreamWriter(csvFile))
{
writer.WriteLine("Name,SerialNumber");

using (DirectoryEntry entry = new DirectoryEntry(ldapPath))
{
using (DirectorySearcher searcher = new DirectorySearcher(entry))
{
searcher.ServerTimeLimit = new TimeSpan(1,0,0);
searcher.Filter = "(objectCategory=computer)";
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("serialNumber");

foreach (SearchResult result in searcher.FindAll())
{
string name = result.Properties["name"][0].ToString();
string serialNumber = result.Properties.Contains("serialNumber") ? result.Properties["serialNumber"][0].ToString() : "";

writer.WriteLine($"{name},{serialNumber}");
}
}
}
}

Console.WriteLine($"AD computers exported to {csvFile}");
}
}
15 Replies
Wolfman
WolfmanOP•11mo ago
I am really unsure if this is a LDAP issue where I am getting a timeout. I have searched online and found something from years ago saying it was a weird error. That was only seen this in association with a Kerberos authentication problem.
mtreit
mtreit•11mo ago
The error is ERROR_DS_LOCAL_ERROR. Have you tried setting the SizeLimit property of the DirectorySearcher to a larger value? Or possibly breaking it up into smaller chunks (like one OU at a time) ? My guess is you are hitting some kind of resource limit but I haven't programmed against Active Directory in quite a while.
Wolfman
WolfmanOP•11mo ago
I am handling only one OU unfortunately. Let me try the size limit. Maybe a memory issue?
mtreit
mtreit•11mo ago
You really have 3 million computers in one OU? 👀
Wolfman
WolfmanOP•11mo ago
Intelligence is beyond us
mtreit
mtreit•11mo ago
In that case you can also try applying a filter to do batches - for instance, apply an LDAP search filter to get all computers starting with the letter A, repeat for B, and so on.
Wolfman
WolfmanOP•11mo ago
Ok I'll try this and come back.
mtreit
mtreit•11mo ago
One of my general rules that I have had to educate a lot of developers on is that search interfaces need to support paging, because at some point you might grow large enough that "Give me everything in one call" will tip over and die. I don't know if DirectorySearcher is doing paging behind the scenes, but it probably should be.
private ArrayList InnerList
{
get
{
if (_innerList == null)
{
_innerList = new ArrayList();
IEnumerator enumerator = new ResultsEnumerator(this, _rootEntry.GetUsername(), _rootEntry.GetPassword(), _rootEntry.AuthenticationType);
while (enumerator.MoveNext())
{
_innerList.Add(enumerator.Current);
}
}
return _innerList;
}
}
private ArrayList InnerList
{
get
{
if (_innerList == null)
{
_innerList = new ArrayList();
IEnumerator enumerator = new ResultsEnumerator(this, _rootEntry.GetUsername(), _rootEntry.GetPassword(), _rootEntry.AuthenticationType);
while (enumerator.MoveNext())
{
_innerList.Add(enumerator.Current);
}
}
return _innerList;
}
}
This doesn't look like paging to me 😬 Also, ArrayList :harold:
Wolfman
WolfmanOP•11mo ago
I never looked inside of it actually... I wonder if I don't use that it may work better I just figured it would be the easier route
mtreit
mtreit•11mo ago
If you don't use what? DirectorySearcher?
Wolfman
WolfmanOP•11mo ago
Yes
mtreit
mtreit•11mo ago
Working directly with LDAP and/or using the COM object directly is probably a pain in the ass... I used to use ldp.exe to directly write LDAP queries and I remember it was painful 🙂
Wolfman
WolfmanOP•11mo ago
Well this seems like a better warning then 😅 let me see what I can do. Thank you for all the help Mtreit
Jimmacle
Jimmacle•11mo ago
i use System.DirectoryServices.Protocols at work, it's cross platform so may use different less ancient and bad techniques i haven't thrown it 3 million entries to search so idk
Wolfman
WolfmanOP•11mo ago
using System;
using System.DirectoryServices;
using System.IO;

class Program
{
static void Main()
{
string ldapPath = "";
string csvFile = "AD_Computers.csv";

using (DirectoryEntry entry = new DirectoryEntry(ldapPath))
{
using (DirectorySearcher searcher = new DirectorySearcher(entry))
{
searcher.Filter = ("(objectClass=computer)");
searcher.SizeLimit = 0;
searcher.PageSize = 100;
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("serialNumber");

// Open the CSV file
using (StreamWriter writer = new StreamWriter(csvFile))
{
int count = 0;
foreach (SearchResult r in searcher.FindAll())
{
if (r.Properties["name"].Count > 0)
{
string name = r.Properties["name"][0].ToString();
string serialNumber = r.Properties.Contains("serialNumber") ? r.Properties["serialNumber"][0].ToString() : $"2.16.840.1.114416.1.63.UnknownESN({name})";
string shortName = name.Length >= 8 ? name.Substring(name.Length - 8) : name;

count++;
// Write the meter directly to the CSV file
writer.WriteLine($"{serialNumber},{shortName}");
Console.WriteLine(count);
}
}
}
}
}

Console.WriteLine($"Exported items to testexport.csv");
Console.ReadLine();
}
}
using System;
using System.DirectoryServices;
using System.IO;

class Program
{
static void Main()
{
string ldapPath = "";
string csvFile = "AD_Computers.csv";

using (DirectoryEntry entry = new DirectoryEntry(ldapPath))
{
using (DirectorySearcher searcher = new DirectorySearcher(entry))
{
searcher.Filter = ("(objectClass=computer)");
searcher.SizeLimit = 0;
searcher.PageSize = 100;
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("serialNumber");

// Open the CSV file
using (StreamWriter writer = new StreamWriter(csvFile))
{
int count = 0;
foreach (SearchResult r in searcher.FindAll())
{
if (r.Properties["name"].Count > 0)
{
string name = r.Properties["name"][0].ToString();
string serialNumber = r.Properties.Contains("serialNumber") ? r.Properties["serialNumber"][0].ToString() : $"2.16.840.1.114416.1.63.UnknownESN({name})";
string shortName = name.Length >= 8 ? name.Substring(name.Length - 8) : name;

count++;
// Write the meter directly to the CSV file
writer.WriteLine($"{serialNumber},{shortName}");
Console.WriteLine(count);
}
}
}
}
}

Console.WriteLine($"Exported items to testexport.csv");
Console.ReadLine();
}
}
Quick update on this I changed the page size to 100 but am missing around 18000 computers I still need. It also takes up a bunch of RAM but cant see an option to free that up besides cleaning up during processing. I have tried DirectoryServices.Protocols. However I cannot seem to get the LDAP to work through it without username and password. Ok I was able to figure it out with Protocols! The memory filling up was from the findAll() statement My issue with the ldap was just I wasn't putting the correct input. It just needed the IP and not full LDAP address url

Did you find this page helpful?