I have a Function that, when activated, iterates through the Crystal Report it is attached to, copies it to a pdf, then mails the entirety of it to a client once per identifying field.
They want to receive a pdf of records grouped by ID, for each ID in the Report, omitting some of a specific ID. I have no idea how to break the Report down into smaller Reports, though, or even where to begin if that's possible in the first place.
I am Creating Each Pdf for individual user and saving to my Google Driver also emailing using SendGrip Api.
I have used this code inside page.Aspx -> aspx.cs file.
//0. Here i am getting list users as an Object:
OpsManagementController OM = new OpsManagementController();
//1. Getting Users List:
var result = OM.UsersGetforInvoice();
//2. Creating folder for Invoices:
string folderName = #"D:\Google Drive\MonthlyInvoices";
string fileName = ("Invoices_" + DateTime.Now.ToString("yyyy-MM-dd").ToString());
string pathString = System.IO.Path.Combine(folderName, fileName);
System.IO.Directory.CreateDirectory(pathString);
string folderNameEmail = #"D:\Google Drive\MonthlyInvoices\Email";
string fileNameEmail = ("Invoices_" + DateTime.Now.ToString("yyyy-MM-dd").ToString());
string pathStringEmail = System.IO.Path.Combine(folderNameEmail, fileNameEmail);
System.IO.Directory.CreateDirectory(pathStringEmail);
//3. Generating invoices by user name:
for (int i = 0; i < result.UserDetail.Count; i++)
{
var userId = result.UserDetail[i].UserID;
var userEmail = result.UserDetail[i].Email;
var userName = result.UserDetail[i].FullName;
userName = userName.Replace(#"C\O", "CO");
userName = userName.Replace(#"C/O", "CO");
// Directories for reports:
var invoicePath = "D:/Google Drive/MonthlyInvoices/" + fileName + "/" + userId + " " + userName + ".pdf";
var invoicePath_email = "D:/Google Drive/MonthlyInvoices/Email/" + fileNameEmail + "/" + userId + " " + userName + ".pdf";
report2.SetParameterValue("UserID", result.UserDetail[i].UserID);
report2.ExportToDisk(ExportFormatType.PortableDocFormat, invoicePath);
// using sendgrip Api :
EmailUtils.SendEmail_Att(
new string[] { userEmail }, //TO : userEmail
new string[] { "email#gmail.com" }, //
invoiceSubject,
invoiceBody,
invoicePath_email
);
}
Related
This below is the code i have. If i comment out the block from object fName to adoc.Close the code works on IIS but if it is not commented out it gives me this error
NullReferenceException: Object reference not set to an instance of an object.]
QuoteProject030117.Controllers.Quote1Controller.Create(Quote1 quote1) +1653
I think iis is having a problem modifying the word doc but i cant find a way to fix it. All of the code works perfectly on Visual Studio when i run it. If anyone could give me a hand would be appreciated. Thank you!
public ActionResult Create([Bind(Include = "id,quotenumber,date,value,won,lost,client,projectdescription,contact")] Quote1 quote1)
{
if (ModelState.IsValid)
{
db.Quote1.Add(quote1);
db.SaveChanges();
string QNsql = #"SELECT quotenumber FROM Quote1 ORDER BY id DESC offset 0 rows fetch next 1 rows only;";
string quoteNum = db.Database.SqlQuery<string>(QNsql).Single();
string Dsql = #"SELECT date FROM Quote1 ORDER BY id DESC offset 0 rows fetch next 1 rows only;";
string date = db.Database.SqlQuery<string>(Dsql).Single();
string Clientsql = #"SELECT client FROM Quote1 ORDER BY id DESC offset 0 rows fetch next 1 rows only;";
string client = db.Database.SqlQuery<string>(Clientsql).Single();
string Contsql = #"SELECT contact FROM Quote1 ORDER BY id DESC offset 0 rows fetch next 1 rows only;";
string contact = db.Database.SqlQuery<string>(Contsql).Single();
string PDessql = #"SELECT projectdescription FROM Quote1 ORDER BY id DESC offset 0 rows fetch next 1 rows only;";
string proDes = db.Database.SqlQuery<string>(PDessql).Single();
System.IO.Directory.CreateDirectory(#"C:\Users\alanf\Documents\CopyTo\" + quoteNum);
System.IO.Directory.CreateDirectory(System.IO.Path.Combine(#"C:\Users\alanf\Documents\CopyTo\" + quoteNum, "Information Recieved From Customer"));
System.IO.Directory.CreateDirectory(System.IO.Path.Combine(#"C:\Users\alanf\Documents\CopyTo\" + quoteNum, "PO Recieved From Customer"));
System.IO.Directory.CreateDirectory(System.IO.Path.Combine(#"C:\Users\alanf\Documents\CopyTo\" + quoteNum, "Project Costing Sheet"));
System.IO.Directory.CreateDirectory(System.IO.Path.Combine(#"C:\Users\alanf\Documents\CopyTo\" + quoteNum, "Quotation Sent To Customer"));
System.IO.Directory.CreateDirectory(System.IO.Path.Combine(#"C:\Users\alanf\Documents\CopyTo\" + quoteNum, "Quotations Recieved From Vendors"));
string fileName = "Quote.docx";
string sourcePath = #"C:\inetpub\wwwroot";
string targetPath = #"C:\Users\alanf\Documents\CopyTo\" + quoteNum + #"\Quotation Sent To Customer";
string sourceFile = System.IO.Path.Combine(sourcePath, fileName);
string destFile = System.IO.Path.Combine(targetPath, fileName);
System.IO.File.Copy(sourceFile, destFile, true);
object fName = System.IO.Path.Combine(targetPath, fileName);
Microsoft.Office.Interop.Word.Application wordApp = new Microsoft.Office.Interop.Word.Application { Visible = false };
Microsoft.Office.Interop.Word.Document aDoc = wordApp.Documents.Open(fName, ReadOnly: false, Visible: false);
aDoc.Activate();
FindAndReplace(wordApp, "<quote>", "" + quoteNum + "");
FindAndReplace(wordApp, "<date>", "" + date + "");
FindAndReplace(wordApp, "<client>", "" + client + "");
FindAndReplace(wordApp, "<contact>", "" + contact + "");
FindAndReplace(wordApp, "<projectdescription>", "" + proDes + "");
aDoc.Close();
Process.Start("explorer.exe", #"C:\Users\alanf\Documents\CopyTo\" + quoteNum);
return RedirectToAction("Index");
}
Office interop from IIS is not supported by Microsoft - see https://social.msdn.microsoft.com/Forums/en-US/1d3923a5-6720-4743-8fa0-c919ff90e4ef/i-cant-open-word-office-on-iis7?forum=worddev and https://support.microsoft.com/en-us/help/257757/considerations-for-server-side-automation-of-office
It would be good to know what line is 1653 as #OmegaMan asked too.
For your scenario, I would recommend looking at a .NET library that supports reading and writing Word documents or doing mail merge. I have used Aspose before, but there are others out there too like GemBox (I haven't used them though).
Also, see this thread Reading doc and docx files using C# without having MS Office installed on server
I am looking to allow a person to to export journal entries into a text file. I can create a file with all the data but rather strictly saving the file somewhere specific I want to allow a user to download and save the file where they want on their computer. How to I force a download of a file after I create it with StreamWriter. I currently have the following code:
string fileName = "Journal.txt";
using (StreamWriter journalExport = new StreamWriter(fileName))
{
foreach (JournalEntryView entry in journalEnteries)
{
//write each journal entery to file/document
journalExport.WriteLine(entry.timestamp + " - " + entry.author + " (" + entry.authorRole + ")");
journalExport.WriteLine(entry.text);
journalExport.WriteLine("");
journalExport.WriteLine("");
}
}
I am also trying to put this into an ActionResult and return the file.
EDIT:
The following code is my new current code and the direction I am looking to go in, but when I use an ActionLink to call this method, i just get redirected to a new page rather than downloading the file.
string fileName = "Journal.txt";
string filepath = ConfigurationManager.AppSettings["DocumentRoot"] + "\\" + id + "\\" + fileName;
using (StreamWriter journalExport = new StreamWriter(filepath))
{
foreach (JournalEntryView entry in journalEnteries)
{
//write each journal entery to file/document
journalExport.WriteLine(entry.timestamp + " - " + entry.author + " (" + entry.authorRole + ")");
journalExport.WriteLine(entry.text);
journalExport.WriteLine("");
journalExport.WriteLine("");
}
}
byte[] fileData = System.IO.File.ReadAllBytes(filepath);
string contentType = MimeMapping.GetMimeMapping(filepath);
var cd = new System.Net.Mime.ContentDisposition
{
FileName = fileName,
Inline = true,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(fileData, contentType);
This might be what you are looking for:
public ActionResult GetFile()
{
...processing stuff...
return File("/files/file.pdf", "application/pdf");
//or
return File("/files/file.pdf", "application/force-download", "donwloadname.pdf");
}
I've been trying like mad to get this to work, and I'm sure it's something simple, but I'm at a loss right now.
I'm using the SDK for DocuSign example. I've modified it lightly to try to get an InPersonSigner embedded:
private Recipient[] ConstructRecipients()
{
// Construct the recipients
var runningList = new List<Recipient>();
for (int i = 1; i <= Request.Form.Count; i++)
{
if (null !=Request.Form[Keys.RecipientName + i])
{
var r = new Recipient
{
UserName = Request.Form[Keys.RecipientName + i],
Email = Request.Form[Keys.RecipientEmail + i] // <-- Using my DocuSign account email for simplicity
};
// Get and set the security settings
string security = Request.Form[Keys.RecipientSecurity + i];
if (null != security)
{
//...Code still here, just making this post shorter
}
}
// Try InPerson Signing
r.RequireIDLookup = false;
r.UserName = "AccountUserName"; //<-- Again, My Account user name for simplicity
r.SignerName = Request.Form[Keys.RecipientName + i]; // "BB King"
r.Type = RecipientTypeCode.InPersonSigner;
r.ID = i.ToString();
//r.Type = RecipientTypeCode.Signer;
if (null == Request.Form[Keys.RecipientInviteToggle + i])
{
// we want an embedded signer
r.CaptiveInfo = new RecipientCaptiveInfo {ClientUserId = i.ToString()};
}
runningList.Add(r);
}
else
{
break;
}
}
return runningList.ToArray();
}
When sending via email, it works, the Host (me) receives the Email, and is able to go through the "In Person Singing Process".
When sending for embedded results (remember I'm using the SDK out of the box for the embedded part - which includes ClientID), it errors with this message: "The recipient you have identified is not a valid recipient of the specified envelope."
What do I need to add to make the Embedded SDK sample work with a In Person Signing Session?
---- EDIT ----
I found the issue, but don't know the best solution. Apparently, the GetStatusAndDocs.aspx.cs file has this line:
DocuSignAPI.FilteredEnvelopeStatuses statuses = client.RequestStatusesEx(filter);
Statuses contains an EnvelopeStatus object, and that contains a RecipientStatus object. The RecipientStatus objects returns the UserName field as the SignerName that I entered, not the UserName that I entered.
This RecipientStatus object doesn't even have a SignerName property/field??? Why?
What property/field should I use to pass to the RequestRecipientToken username parameter? If it's one of my field agents, I need to know how to fully identify the person and their account to determine they went to the field, fired up the app, and then did an in person signing.
Still at a lost although I have figured this out so far?
I found the answer:
For an embedded RecipientTypeCode.InPersonSigner, the email address can be bogus, fake, junk, etc., (as long as it's in email format "somethingdotsomething#someplacedotsomething.whatever").
When it's RecipientTypeCode.InPersonSigner, the username you pass to the RequestRecipientToken() is the Actual Account Holder userName. Not the account that you may have given access to your main account as an Agent or someone part of a group, and not the signer/recipient name, but the account holder name that is on the account credentials being used for embedded signature.
The code on page GetStatusAndDocs.aspx.cs can be modified as follows to accomplish this:
protected System.Web.UI.HtmlControls.HtmlGenericControl CreateEnvelopeTable(DocuSignAPI.EnvelopeStatus status)
{
var envelopeDiv = new System.Web.UI.HtmlControls.HtmlGenericControl("div");
int recipIndex = 0;
foreach (DocuSignAPI.RecipientStatus recipient in status.RecipientStatuses)
{
var info = new System.Web.UI.HtmlControls.HtmlGenericControl("p");
String recipId = "Recipient_Detail_" + status.EnvelopeID + "_" + recipient.RoutingOrder + "_" + recipient.UserName + "_" + recipient.Email + "_" + recipIndex++;
info.InnerHtml = "<img src=\"images/plus.png\"> Recipient - " +
recipient.UserName + ": " + recipient.Status.ToString();
if (recipient.Status != DocuSignAPI.RecipientStatusCode.Completed && null != recipient.ClientUserId)
{
// For InPersonSigner, this will not work because the recipient.UserName needs to be the credentialed account actual user name, not the recipieint userName.
//info.InnerHtml += " <input type=\"submit\" id=\"" + status.EnvelopeID + "\" value=\"Start Signing\" name=\"DocEnvelope+" + status.EnvelopeID + "&Email+" + recipient.Email + "&UserName+" +
// recipient.UserName + "&CID+" + recipient.ClientUserId + "\">";
// In order to make this work for InPersonSigner, we need the envelope account (the credentialed account) userName instead of recipient.UserName
// Get correct user name depending on recipient type
string userName = (recipient.Type == RecipientTypeCode.InPersonSigner) ? status.ACHolder : recipient.UserName;
info.InnerHtml += " <input type=\"submit\" id=\"" + status.EnvelopeID + "\" value=\"Start Signing\" name=\"DocEnvelope+" + status.EnvelopeID + "&Email+" + recipient.Email + "&UserName+" +
userName + "&CID+" + recipient.ClientUserId + "\">";
}
if (null != recipient.TabStatuses)
{
// Code here is the same, just making it shorter for this response
}
envelopeDiv.Controls.Add(info);
}
// Code between here and return is the same, just making it shorter for this response
return envelopeDiv;
}
I am using the HTML5 canvas element and the new HTML5 file i\o function to drop multiple files on it and have them upload. It works fine, but now I need to generate a new filename if no files are in the destination directory (It's a 7 digit integer) or get the name of the last uploaded file, convert it to int32 and increment that by one for every new file being uploaded to the same directory. This is where the GetFileName(dir); comes in. The first image always uploads fine but the problem begins once the second file is saved and the process hits ImageJob.Build(), I presume this is because once the new file is starting to write, the GetFile() method runs for second file in line simultaneously and is checking for last written file, which is still being written and this creates the conflict. How can I fix this, maybe I can somehow itterate with a foreach over the Request.InputStream data or implement some kind process watch that waits for the process to finish?
Update: I tried using TempData to store the generated filename, and just increment on the int value in TempData for all the next file names and it appears to do better, gets more images in but still errors at some point. But TempData is not for that as it gets erased after each read, reassigning to it again does not help. Maybe I'll try storing it in session.
The process cannot access the file 'C:\Users\Admin\Documents\Visual Studio
2010\Projects\myproj\myproj\Content\photoAlbums\59\31\9337822.jpg'
because it is being used by another process.
public PartialViewResult Upload()
{
string fileName = Request.Headers["filename"];
string catid = Request.Headers["catid"];
string pageid = Request.Headers["pageid"];
string albumname = Request.Headers["albumname"];
var dir = "~/Content/photoAlbums/" + catid + "/" + pageid + "/" + (albumname ?? null);
var noex = GetFileName(dir);
var extension = ".jpg";
string thumbFile = noex + "_t" + extension;
fileName = noex + extension;
byte[] file = new byte[Request.ContentLength];
Request.InputStream.Read(file, 0, Request.ContentLength);
string imgdir;
string thumbimgdir;
string imageurl;
if (albumname != null)
{
imgdir = Server.MapPath("~/Content/photoAlbums/" + catid + "/" + pageid + "/" + albumname + "/" + fileName);
thumbimgdir = Server.MapPath("~/Content/photoAlbums/" + catid + "/" + pageid + "/" + albumname + "/" + thumbFile);
imageurl = "/Content/photoAlbums/" + catid + "/" + pageid + "/" + albumname + "/" + thumbFile;
}
else
{
imgdir = Server.MapPath("~/Content/photoAlbums/" + catid + "/" + pageid + "/" + fileName);
thumbimgdir = Server.MapPath("~/Content/photoAlbums/" + catid + "/" + pageid + "/" + thumbFile);
imageurl = "/Content/photoAlbums/" + catid + "/" + pageid + "/" + thumbFile;
}
ImageJob b = new ImageJob(file, imgdir, new ResizeSettings("maxwidth=1024&maxheight=768&format=jpg")); b.CreateParentDirectory = true; b.Build();
ImageJob a = new ImageJob(file, thumbimgdir, new ResizeSettings("w=100&h=100&mode=crop&format=jpg")); a.CreateParentDirectory = true; a.Build();
ViewBag.CatID = catid;
ViewBag.PageID = pageid;
ViewBag.FileName = fileName;
return PartialView("AlbumImage", imageurl);
}
public string GetFileName(string dir)
{
var FullPath = Server.MapPath(dir);
var dinfo = new DirectoryInfo(FullPath);
string FileName;
if (dinfo.Exists)
{
var Filex = dinfo.EnumerateFiles().OrderBy(x => x.Name).LastOrDefault();
FileName = Filex != null ? Path.GetFileNameWithoutExtension(Filex.Name) : null;
if (FileName != null)
{
FileName = FileName.Contains("_t") ? FileName.Substring(0, FileName.Length - 2) : FileName;
int fnum;
Int32.TryParse(FileName, out fnum);
FileName = (fnum + 1).ToString();
if (fnum > 999999) { return FileName; } //Check that TryParse produced valid int
else
{
var random = new Random();
FileName = random.Next(1000000, 9999000).ToString();
}
}
else
{
var random = new Random();
FileName = random.Next(1000000, 9999000).ToString();
}
}
else
{
var random = new Random();
FileName = random.Next(1000000, 9999000).ToString();
}
return FileName;
}
You simply cannot use the Random class if you want to generate unique filenames. It uses the current time as the seed, so two exactly concurrent requests will always produce the same 'random' number.
You could use a cryptographic random number generator,
but you would still have to ensure that (a) only one thread would generate it at a time, and (b) you used a sufficiently long identifier to prevent the Birthday paradox.
Thus, I suggest that everyone use GUID identifiers for their uploads, as they solve all of the above issues inherently (I believe an OS-level lock is used to prevent duplicates).
Your method also doesn't handle multiple file uploads per-request, although that may be intentional. You can support those by looping through Request.Files and passing each HttpPostedFile instance directly into the ImageJob.
Here's a simplified version of your code that uses GUIDs and won't encounter concurrency issues.
public PartialViewResult Upload()
{
string albumname = Request.Headers["albumname"];
string baseDir = "~/Content/photoAlbums/" + Request.Headers["catid"] + "/" + Request.Headers["pageid"] + "/" (albumname != null ? albumname + "/" : "");
byte[] file = new byte[Request.ContentLength];
Request.InputStream.Read(file, 0, Request.ContentLength);
ImageJob b = new ImageJob(file, baseDir + "<guid>.<ext>", new ResizeSettings("maxwidth=1024&maxheight=768&format=jpg")); b.CreateParentDirectory = true; b.Build();
ImageJob a = new ImageJob(file, baseDir + "<guid>_t.<ext>", new ResizeSettings("w=100&h=100&mode=crop&format=jpg")); a.CreateParentDirectory = true; a.Build();
//Want both the have the same GUID? Pull it from the previous job.
//string ext = PathUtils.GetExtension(b.FinalPath);
//ImageJob a = new ImageJob(file, PathUtils.RemoveExtension(a.FinalPath) + "_t." + ext, new ResizeSettings("w=100&h=100&mode=crop&format=jpg")); a.CreateParentDirectory = true; a.Build();
ViewBag.CatID = Request.Headers["catid"];
ViewBag.PageID = Request.Headers["pageid"];
ViewBag.FileName = Request.Headers["filename"];
return PartialView("AlbumImage", PathUtils.GuessVirtualPath(a.FinalPath));
}
If the process is relatively quick (small files) you could go in a loop, check for that exception, sleep the thread for a couple of seconds, and try again (up to a maximum number of iterations). One caveat is that if the upload is asynchronous you might miss a file.
A couple of other suggestions:
Make the GetFileName to be a private method so that it doesn't get triggered from the web.
The OrderBy in the Filex query might not do what you expect once the it goes to 8 digits (possible if the first Random() is a very high number).
The Random() should probably be seeded to produce better randomness.
I have written the following program which connects to two LDAP stores, compares the attributes and based on the outcome, creates a new csv file. I have run into a problem though.
Here is the code:
//Define LDAP Connection
string username = "****";
string password = "*****";
string domain = "LDAP://****";
//Define LDAP Connection
string ABSAusername = "****";
string ABSApassword = "****";
string ABSAdomain = "LDAP://****";
//Create Directory Searcher
DirectoryEntry ldapConnection = new DirectoryEntry(domain,username,password);
ldapConnection.AuthenticationType = AuthenticationTypes.Anonymous;
DirectorySearcher ds = new DirectorySearcher(ldapConnection);
ds.Filter = "((EmploymentStatus=0))";
ds.SearchScope = System.DirectoryServices.SearchScope.Subtree;
//Create Directory Searcher
DirectoryEntry ABSAldapConnection = new DirectoryEntry(ABSAdomain, ABSAusername, ABSApassword);
ABSAldapConnection.AuthenticationType = AuthenticationTypes.Anonymous;
DirectorySearcher ABSAds = new DirectorySearcher(ABSAldapConnection);
ABSAds.Filter = "((&(EmploymentStatus=3)(EmploymentStatusDescription=Active))";
ABSAds.SearchScope = System.DirectoryServices.SearchScope.Subtree;
ds.PropertiesToLoad.Add("cn");
ds.PropertiesToLoad.Add ("uid");
ds.PropertiesToLoad.Add("sn");
ds.PropertiesToLoad.Add("PersonnelAreaDesc");
ds.PropertiesToLoad.Add("JobcodeID");
ds.PropertiesToLoad.Add("CostCentreID");
ds.PropertiesToLoad.Add("CostCentreDescription");
ds.PropertiesToLoad.Add ("givenName");
ds.PropertiesToLoad.Add ("EmploymentStatus");
ds.PropertiesToLoad.Add("EmploymentStatusDescription");
ABSAds.PropertiesToLoad.Add("uid");
ABSAds.PropertiesToLoad.Add("EmploymentStatus");
ABSAds.Sort = new SortOption("uid", SortDirection.Ascending);
ds.Sort = new SortOption("cn", SortDirection.Ascending);
SearchResultCollection absaUsers = ds.FindAll();
SearchResultCollection srcUsers = ds.FindAll();
sw.WriteLine("Action" + "," + "uid" + "," + "Business Area" + "," + "employeeNumber" + "," + "First Name" + "," + "Last Name" + "," + "JobCodeID" + "," + "costCentreID" + "," + "costCentreDescription" + "," + "FullName" + "," + "EmploymentStatus" + "," + "EmploymentStatusDescription" );
sw.WriteLine("");
foreach (SearchResult users in srcUsers)
{
string cn = users.Properties["cn"][0].ToString();
string sn = users.Properties["sn"][0].ToString();
string userID = users.Properties["uid"][0].ToString();
string description = users.Properties["PersonnelAreaDesc"][0].ToString();
// string jobCodeID = users.Properties["JobcodeID"][1].ToString();
string CostCentreID = users.Properties["costCentreID"][0].ToString();
string CostCentreDescription = users.Properties["CostCentreDescription"][0].ToString();
string givenName = users.Properties["givenName"][0].ToString();
string employmentStatus = users.Properties["EmploymentStatus"][0].ToString();
string EmploymentStatusDescription = users.Properties["EmploymentStatusDescription"][0].ToString();
foreach (SearchResult absaUser in absaUsers)
{
string absaUID = absaUser.Properties["uid"][0].ToString();
string absaEmploymentStatus = absaUser.Properties["EmploymentStatus"][0].ToString();
if (cn == absaUID)
{
if (absaEmploymentStatus == "3")
{
sw.WriteLine(cn);
}
}
}
}
sw.Flush();
sw.Close();
sw.Dispose();
}
}
I created two foreach loops, in the first loop i assigned variables to strings, and in the second foreach loop i do a comparison with an IF statement. What i want to do is: if the uid in one LDAP is equal to the uid in the other ldap, and if the status of the user in the 1st ldap = 0 but the status of the user in the 2nd ldap = 3: then i want to print out the users that match that criteria from the 1st ldap.
If you look through my code, am i doing somthing wrong? The output of the program currently is about 10 users that are duplicated atleast 100 times each.
Thanks in advance.
There are several things obviously wrong with this code....
1) First of all, you're creating the same search result twice:
SearchResultCollection absaUsers = ds.FindAll();
SearchResultCollection srcUsers = ds.FindAll();
So if that searcher finds 10 users, you have two collections of the same 10 users here.
2) You then used nested foreach loops, so you basically go through all results from the first collection, one by one, and for each of those entries, you enumerate the entire second collection - basically doing a cartesian product between the two collections. So that's why you end up with 10 x 10 = 100 users in the end.....
3) you seem to be missing some kind of a restriction/condition to pick out only those elements from your second/inner result set that match the outer/first result set in some way. As long as you don't have such a condition in place, you'll always get all the results from the second result set for each element of the first result set = a classic cartesian product. Somehow, you want to select only certain elements from the second set, based on something from the first result set......
You have missed break in the following place:
if (cn == absaUID && absaEmploymentStatus == "3")
{
sw.WriteLine(cn);
break;
}