Querying LDAP in C# to get list of computers - c#

I am working with LDAP in my Windows Forms C# project.
I created a CheckListBox, and started to create a method that queries the Active Directory for all my computers is the environment.
The method is:
public string ComputerList()
{
DirectoryEntry rootDSE = new DirectoryEntry("LDAP://MyDomain.Local");
DirectorySearcher computerSercher = new DirectorySearcher(rootDSE);
computerSercher.PageSize = 10000;
computerSercher.Filter = "(&(objectClass=computer))";
}
I am also have as I said, a CheckListBox.
What I want to do is to have a result for the query and every computer that founds. add it to the Items property for the CheckListBox.
But I don't how even approach the result. it's not like PowerShell that gives you a list of objects...
Thank you

You're almost there. A few things:
Set the page size to 1000. AD won't give you any more than 1000 at a time, so if you set it to anything over that you'll only get 1000 (if DirectorySearcher doesn't get back what it considers a full page, it'll stop asking)
Add the attributes you want to read to the PropertiesToLoad collection. If you don't add anything, it'll give you every attribute with a value, which is a bunch of unnecessary data you won't use. You'll likely only want to see the cn attribute (Common Name).
Use FindAll() to get the results. Make sure you wrap this in a using statement to prevent memory leaks (the documentation says so).
When you look at the results, every property is presented as an array, whether it is or not in AD. So you'll need to use [0] in most cases. For future reference (not applicable here): if a property is not set in AD, it won't be in the Properties collection at all, so, for optional attributes, you'll have to use Properties.Contains() to see if it's there first.
Working from what you have, here is a method that will return a list of computer names:
public IEnumerable<string> ComputerList()
{
DirectoryEntry rootDSE = new DirectoryEntry("LDAP://MyDomain.Local");
DirectorySearcher computerSercher = new DirectorySearcher(rootDSE)
{
PageSize = 1000,
Filter = "(&(objectClass=computer))"
};
computerSercher.PropertiesToLoad.Add("cn");
using (var results = computerSercher.FindAll())
{
foreach (SearchResult result in results)
{
yield return (string) result.Properties["cn"][0];
}
}
}
Update: To answer your questions in your comment:
The yield basically tells it to "add this item to the collection that will be returned". There is a little more going on in the background, which you can read about here. But in simplest terms, it saves you from having to create your own list, add items to that list and return the list.
I changed the return type from string to IEnumerable<string> because you are getting multiple results from your search, so I assume you want to return all of those results. This method will give you a list of computer names, not just one computer name.
FindAll() returns a SearchResultCollection. For some reason I don't know, the objects returned from SearchResultCollection in a foreach are presented as object. So you need to cast them to SearchResult explicitly to use them.

Related

Outlook add-in - Store and look up data as hidden StorageItem

I want to create a "local database" in Outlook, by storing some auxiliary data in a hidden form.
After investigating the object model, I found the StorageItem objects, that looked promising.
I managed to store some rows, and also look them up by their unique subject, however, I have trouble iterating through the StorageItems. It says in the object model, that there's no direct iterator available for the StorageItem object, but I could query a table from the same folder, that will have the StorageItems.
The table will end up containing 5 items, and none of them look anything like my stored items.
Here's my code that I currently use for inserting the StorageItems with some custom properties:
public static void InsertStorageItem(String UniqueID, String Name, String Address)
{
Outlook.NameSpace ns = null;
Outlook.Folder tasksFolder = null;
ns = outlookApp.Session;
tasksFolder = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderTasks) as Outlook.Folder;
Outlook.StorageItem OIEDBTask = tasksFolder.GetStorage(UniqueID, Microsoft.Office.Interop.Outlook.OlStorageIdentifierType.olIdentifyBySubject);
if (OIEDBTask.Size == 0)
{
OIEDBTask.UserProperties.Add("uniqueid", Microsoft.Office.Interop.Outlook.OlUserPropertyType.olText);
OIEDBTask.UserProperties.Add("name", Microsoft.Office.Interop.Outlook.OlUserPropertyType.olText);
OIEDBTask.UserProperties.Add("address", Microsoft.Office.Interop.Outlook.OlUserPropertyType.olText);
}
//assign values to custom properties
OIEDBTask.UserProperties["uniqueid"].Value = UniqueID;
OIEDBTask.UserProperties["name"].Value = Name;
OIEDBTask.UserProperties["address"].Value = Address;
OIEDBTask.Save();
}
Here's how I would like to query data:
public static dynamic QueryStorageItem()
{
List<dynamic> QueryResults = new List<dynamic>();
Outlook.NameSpace ns = null;
Outlook.Folder tasksFolder = null;
Outlook.Table ResultTable = null;
ns = outlookApp.Session;
tasksFolder = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderTasks) as Outlook.Folder;
String Filter = "[LastModificationTime] > '5/1/2005'"; //tried removing the filter, but it didn't help. the comparison date is very old, so it should always give back some data
ResultTable = tasksFolder.GetTable(Filter, Outlook.OlTableContents.olHiddenItems);
while(!ResultTable.EndOfTable) //tried a do-while structure also, didn't work.
{
Outlook.Row resultRow = ResultTable.GetNextRow();
QueryResults.Add(resultRow.GetValues()); //the GetValues() function will give me an array with 5 objects in it, that are very different than my StorageItems
}
return QueryResults;
}
The main idea would be, to use the set up properties of the StorageItems, to use the Filtering, that is given by Outlook: for example, try to find all StorageItems, where the address is a certain value.
Naturally, I would write a similar Filter string:
String Filter = "[address] = 'certain value'";
This doesn't work, and after reading up a little bit on it, I came to the conclusion, that it might not work, because they are custom UserProperties, and filtering should only work on built-in properties for the Outlook items, such as LastModificationTime and others.
However, even without filtering, the data set that I get back from the GetTable function is of no use. (So I can't iterate through the rows, and they don't represent any data or even structure, that I've inserted)
Any chance somebody could guide towards a better solution?
My main scope is, to store some auxiliary data, that is not visible to the user. If this is not the solution, then I would have to create tasks instead of StorageItems.
Storage (aka associated) items are just regular messages from the MAPI point of view (you can see them in OutlookSpy (I am its author) if you click IMAPIFolder button and go to the "Associated Contents" tab), it is just OOM decides to expose them very differently. Storage items in OOM are not really designed to be searched - the idea is that you create/open your private item by the subject or the message class and do not care or touch other storage items that you know nothing about.
One trick I can think of is to construct message class using the address itself (e.g. "IPM.Note.user#domain.demo") and specify that message class when opening the storage item.
If you really want to search the associated table of contents, Extended MAPI (C++ or Delphi) or Redemption (I am also its author) are probably your only choices. Redemption exposes associated items in the same way it exposes the regular items, i.e. RDOFolder.HiddentItems vs RDOFolder.Items. You can then search using RDOItems.Find/FindNext/Restrict/MAPITable.ExecSQL.

How to check if a value exists in a list?

I'm relatively new to C# programming (programming as a whole, actually), but I've built an application to manage the application pools on a server that my team at work uses. It does everything it's supposed to fairly well, but the only issue I'm running into is in saving previously-used configurations to the app.config file so the user doesn't have to put them in manually every time. As it stands, I can save to and load from the file magnificently (along with all of the strings I need in each group).
The issue is that I want to do a cursory check to see if a Name string exists in the group before writing it. Example of the part of the app.config:
<appSettings>
<add Name="RowName" MachineName="MS-02348" AppSrvName="AppServer" WebSrvName="AppNet"/>
<add Name="RowName2" MachineName="MS-68186" AppSrvName="AppServer2" WebSrvName="AppNet2"/>
</appSettings>
So what I'm currently doing to load the values is I have a method that retrieves the appSettings/add nodes and throws them into a list, then sets the values to properties of an object. The reason I do this is so that I can have a drop-down that lists only the Name of an object, and then the rest of the information is all available for when I call the method on the selected item.
Anyway, what I'm running into now is that I want to make sure that if the Name already exists in the app.config, I prompt the user to write another name instead of saving it to the database. Having two child nodes with the same "Name" value would wreak havoc on my logic.
I tried a foreach to cycle through the objects in the list, but without knowing how many objects there could be I didn't know of an easy way of really saying it does or does not exist. I've also tried targeting the childnode based on the values listed in the node, but it seems to fail there too. I'm guessing that part is syntax, but it seems to match up with how the method list defines it.
Any thoughts?
if (list.Any())
{
// found something!
}
else
{
// found nothing
}
I always use Any() simply because it's the most performant. List.Count() goes through each item and counts them, but you don't care about the number of items -- you only care if there's an item at all. Any() will enumerate through the list and stop if it finds an item. So, in the extreme case, in a list of a million items Count() will enumerate every single one and return while Any() will enumerate one and return.
Plus, it returns a bool, which is handy for more concise code. :)
As an added bonus, you can call Any() looking for specific things. So, in a list of people I can look to see if there are any people older than 21 in it:
if (list.Any(person => person.Age > 21))
{
// ...
}
Edit: Formatting.
maybe something like this
var list = new List<AppSettings>();
var item = list.FirstOrDefault(x => x.Name == NameEnteredByUser);
if (item == null)
{
//there is no such item
}
else
{
//notify the user
}
or the Any extension method:
var list = new List<AppSettings>();
if (list.Any(x => x.Name == NameEnteredByUser))
{
//name exists
}
else
{
//no such name used before
}
As a sidenote, have a unique field configured in your database so that when your programming logic fails you wont enter a record. corrupt data state in db is bad.
neo112 is correct in his logic, but I am unsure if the main problem you have is performance related, since you mention you dont know if it may get too long.
First, you could also do the following;
int count = list.Count(a => a.Name == NameEnteredByUser);
if(count > 0)
{
// exists
}
I believe .Count() is faster than .First() (anecdotal evidence only) and personally think it's a bit cleaner.
Also, another thing you could try to do is to sort your list by name when adding to the appSettings node. Then, you should instantiate a SortedList instead of just List, then that would also (possitively) affect performance. But, I am unsure if sorting is an option for you.

Modifying a class in a collection with undo

This is not so much a question as to how to do something, but rather how I can implement something better or think about the problem differently.
I have a winforms application that allows the user to select multiple rows in a grid. These rows represent accounts and when the user selects the accounts and hits a button, a boolean property on the objects will change to whatever the selected value is regardless of it's existing state. However, if a validation method fails, a message is sent to the user and the boolean property needs to be set back to it's original state.
public void ModifyAccounts(List<DemoAccount> accts, bool updateIsSpecial)
{
// Dictionary that will hold the account ID along with the booleans original state
Dictionary<int, bool> originalState = new Dictionary<int, bool>();
foreach(var acct in accts)
{
// Add the current state to the dictionary
originalState.Add(acct.Id, acct.IsSpecial);
acct.IsSpecial = updateIsSpecial;
}
// Send the list to another method that loops through each account and checks
// for specific validation rules. Returns a collection of tuples. The tuple
// contains the account for item1 and a bool validated flag for item2
var valAccounts = ValidateAccounts(accts);
var failedAccounts = from obj in valAccounts
where !acct.Item2
select new
{
account = obj.Item1,
isValid = obj.Item2
};
if (failedAccounts.Count() > 0)
{
// Alert the user with custom msg box method that the accounts failed
// Do Custom Method
// Reset the values of the failed accounts to their previous state.
// It is possible that some accounts passed validation and were saved,
// only the failed accounts should be reset.
foreach (var obj in failedAccounts)
{
bool originalFlagState = false;
originalFlagStates.TryGetValue(obj.account.Id, out originalFlagState);
var origAccount = accts.Where(x => x.Id == obj.account.Id).FirstOrDefault();
origAccount.IsSpecial = originalFlagState;
}
}
}
I hope this isn't too confusing. I only have ~3 years of dev experience which is not a lot. However, I feel it's enough to understand when working on something that if it feels like there is a better way then I am probably not doing it correctly or efficiently. Modifying the account flag changes the object in the accounts list. Obviously adding the object to a new list will just create a reference to that object. So I can't do something like holding 2 collections one for modification and the other for original state. I also can't do a deep copy because the account class is not marked serializable. I cannot change this because of the type of object.
Thanks to anyone who can provide some advice or insight!
I do not understand why you want to validate after changing, and not beforehand.
But if you really have to validate and use undo afterwards there is a design pattern which supports this behavior
Have a look at the command pattern. Here is a code project link that describes how to implement it
http://www.codeproject.com/Articles/8303/Using-the-Command-pattern-for-undo-functionality
In your particular case I would iterate through the command stack and undo all commands which are used on failed accounts
It could look something like this
foreach (var obj in failedAccounts)
foreach ICommand command in commandStack
If(command.element.Equals(obj))
command.undo;
command.element should contain the element you want to change with your command.
if you dont want two foreach u could use System.Linq operations

Understanding how a property of type List<> works

I got to think I don't understand how it works.
My specific question is: Why am I allowed to set the value of a list property element when I have no setter and no backing list variable?
Let me explain. Let's say I have a CustomerTable class with:
public List<string> Name
{
get
{
var names = new List<string>();
foreach (CustomerRow row in Rows)
{
name.Add(row.Name);
}
return names;
}
}
The idea is to have a read-only property show the contents of a column without duplicating data in my class, since I already have a list of rows.
Anyway, my surprise comes when pieces of code like the following one are accepted by Visual Studio without claiming any kind of error (and it even allows me to compile without errors):
Name[0] = "John";
I can't understand why this is legal. My property has no set { }, and it doesn't even have a backing list to modify. What is this piece of code supposed to do?
Shouldn't it work like a method? Is there really a stored list other than the one I generate each time someone "gets" it?
(I can give more details on demand and will also be grateful for any other remarks)
You are not setting the property, rather you are getting the property (which is a list) and then operating on it (in your example, changing its first member). If you were to try:
Name = new List<string>();
You would get the compilation error you were expecting to get. Note that since you are creating a new list every time, your Rows property remains read-only (assuming it is not exposed somewhere else). If you want to make it clear that changes to your returned collection are meaningless, you can change the type of the Name property to IEnumerable<string>:
public IEnumerable<string> Name
{
get
{
return Rows.Select(row => row.name); //LINQ is more elegant here
}
}
In your example you don't set Name (which is read-only, indeed), but you set the first list element contained in Name, which is Name[0], and there's no reason why you could not do that since List<string> is an object type which allows to set elements.

LINQ Set Operations not working (Intersect, Except)

I'd like to use the Uber-Coolness of LINQ set operations to express the following :
foreach (Group group in groups)
{
if (user.Groups.Contains(group))
{
assignedGroups.Add(group);
}
else
{
availableGroups.Add(group);
}
}
I thought it should be a two-liner achieving this :
var assigned = user.Groups.Intersect(groups);
var available = groups.Except(user.Groups);
Whenever I run this example the foreach approach fills my lists correctly, while the set operations result in an empty assigned list and a filled available list.
I thought it must be a problem concerning the equality check, but the fact that Contains() is working proves this wrong.
Can anyone help me see my misconception here?
the IEnumerable groups is also result of a LINQ query, just in case that information is of some help...
Well, it shouldn't make a difference, but from the point of view of symmetry I'd reverse how you're creating assigned. I'd also make sure that the query is only executed once, and that the remaining operations occur in-process:
var cachedGroups = groups.ToList();
var assigned = cachedGroups.Intersect(user.Groups);
var available = cachedGroups.Except(user.Groups);
One possibility is that user.Groups has a custom equality comparer. That would explain why the foreach version worked but the LINQ version didn't. What's the type of user.Groups, and how much do you know about the equality comparer it's using?

Categories

Resources