I need to browse ActiveDirectory in order to select computer objects. As I find out, there are some containers that could have computer items, and the other can contain users, group policies, etc. I only want to show the containers which contains computers. So I use this code to check if container contains any computer:
public static bool CheckContainsComputers(DirectoryEntry entry)
{
using (DirectorySearcher ds =
new DirectorySearcher(entry, "(objectCategory=computer)", new string[0], SearchScope.Subtree))
{
ds.Asynchronous = true;
ds.SizeLimit = 1;
try
{
SearchResult sr = ds.FindOne();
return (sr == null) ? false : true;
}
catch
{
return false;
}
}
}
Questions:
In order to reduce the count of call to this method I want to know - is it possible to find out if DirectoryEntry can contain computers without running the DirectorySearcher?
Is it possible with one call to DirectorySearcher with SearchScope.OneLevel to find containers, that can have computers, and computers
1.If you already have your directoryEntry, there is no need to search for it again.
I think what you want is something like this :
if (entry.Properties["objectCategory"].Value.ToString().Contains("Computer"))
return true;
else
return false;
2.
Of course !
DirectoryEntry de = new DirectoryEntry("LDAP://myldapserver.com");
DirectorySearcher directorySearcher = new DirectorySearcher(de);
directorySearcher.SearchScope = SearchScope.OneLevel;
directorySearcher.Filter = "(objectCategory=computer)";
SearchResultCollection srCollection = directorySearcher.FindAll();
Related
I am using System.DirectoryServices.DirectoryEntry to create AD user and everything work fine except for some Remote Desktop specifics properties.
Exemple :
newUser.Properties["msTSConnectClientDrives"].Value = false;
newUser.Properties["msTSConnectPrinterDrives"].Value = false;
newUser.Properties["msTSDefaultToMainPrinter"].Value = false;
This doesn't throw any exception, so I guess the properties are found in the object but they don't have any effect. When I go into the property window of that user, under "Environment" tab, these 3 checkbox are still checked on.
Am I missing something particular for these properties ?
Thank for your help.
EDIT :
Sorry I have been really busy, here is a code sample :
private string CreateNewADAccount(string accountName, string accountPassword)
{
try
{
PrincipalContext context = new PrincipalContext(ContextType.Domain, "SV-LITE", #"LITE\xxxxxxxx", "yyyyyyyy");
UserPrincipal newUser = new UserPrincipal(context);
newUser.SamAccountName = accountName;
newUser.UserPrincipalName = accountName;
newUser.Name = "LiteUser2015 - " + accountName;
newUser.DisplayName = "LiteUser2015 - " + accountName;
newUser.SetPassword(accountPassword);
newUser.PasswordNeverExpires = true;
newUser.UserCannotChangePassword = true;
newUser.Save();
// Set advanced properties
if (newUser.GetUnderlyingObjectType() == typeof(DirectoryEntry))
{
DirectoryEntry entry = (DirectoryEntry)newUser.GetUnderlyingObject();
entry.Properties["msTSConnectClientDrives"].Value = false;
entry.Properties["msTSConnectPrinterDrives"].Value = false;
entry.Properties["msTSDefaultToMainPrinter"].Value = false;
entry.Properties["msTSInitialProgram"].Value = "test";
entry.CommitChanges();
}
return newUser.Guid.ToString();
}
catch (Exception e)
{
MessageBox.Show("Failed to create PrincipalContext. Exception: " + e);
}
return null;
}
After making the changes, you have to call CommitChanges - newUser.CommitChanges();
See https://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.commitchanges%28v=vs.110%29.aspx
By default, changes to properties are made locally to a cache..
It might have something to do with the Server OS version you're using. I found this answer on another site that talks about Windows 2000 and 2003. It should work for Windows2008 and above:
For 2000 / 2003 you have to access them using the Terminal Services
ADSI extension. The reference for that is here:
http://msdn.microsoft.com/en-us/library/aa380823(VS.85).aspx
When I retrieve members of a local WinNT group, someway somehow not all members are returned. I do add:
Active Directory users
Active Directory groups
Both successful (see picture), but only the users show up afterwards.
Question is:
What happens to added groups?
See last method in code sample 'GetMembers()'
Is this a known issue?
Any workaround available?
Many thanks!!
string _domainName = #"MYDOMAIN";
string _basePath = #"WinNT://MYDOMAIN/myserver";
string _userName = #"MYDOMAIN\SvcAccount";
string _password = #"********";
void Main()
{
CreateGroup("lg_TestGroup");
AddMember("lg_TestGroup", #"m.y.username");
AddMember("lg_TestGroup", #"Test_DomainGroup");
GetMembers("lg_TestGroup");
}
// Method added for reference.
void CreateGroup(string accountName)
{
using (DirectoryEntry rootEntry = new DirectoryEntry(_basePath, _userName, _password))
{
DirectoryEntry newEntry = rootEntry.Children.Add(accountName, "group");
newEntry.CommitChanges();
}
}
// Add Active Directory member to the local group.
void AddMember(string groupAccountName, string userName)
{
string path = string.Format(#"{0}/{1}", _basePath, groupAccountName);
using (DirectoryEntry entry = new DirectoryEntry(path, _userName, _password))
{
userName = string.Format("WinNT://{0}/{1}", _domainName, userName);
entry.Invoke("Add", new object[] { userName });
entry.CommitChanges();
}
}
// Get all members of the local group.
void GetMembers(string groupAccountName)
{
string path = string.Format(#"{0}/{1}", _basePath, groupAccountName);
using (DirectoryEntry entry = new DirectoryEntry(path, _userName, _password))
{
foreach (object member in (IEnumerable) entry.Invoke("Members"))
{
using (DirectoryEntry memberEntry = new DirectoryEntry(member))
{
string accountName = memberEntry.Path.Replace(string.Format("WinNT://{0}/", _domainName), string.Format(#"{0}\", _domainName));
Console.WriteLine("- " + accountName); // No groups displayed...
}
}
}
}
Update #1
The sequence of the group members seems to be essential. As soon as the enumerator in GetMembers() stumbles on an Active Directory group, the remaining items are not displayed either. So if 'Test_DomainGroup' is listed first in this example, GetMembers() does not display anything at all.
I know it's an old question and you've likely found the answers you need, but just in case someone else stumbles accross this...
The WinNT ADSI provider you're using in your DirectoryEntry [ie. WinNT://MYDOMAIN/myserver] has pretty limited capabilities for working with Windows Domains that are not stuck in the old Windows 2000/NT functional level (https://support.microsoft.com/en-us/kb/322692).
In this case the problem is that the WinNT provider doesn't know how to handle Global or Universal security groups (which didn't exist in Windows NT and are activated as soon as you raise your domain level above Windows 2000 mixed mode). So, if any groups of those types are nested under a local group you'll generally have problems like the one you described.
The only solution/workaround I've found is to determine if the group you're enumerating is from a domain and if so, then switch to the LDAP provider which will display all members properly when invoking "Members".
Unfortunately I don't know of an "easy" way to just switch from using the WinNT provider to using the LDAP provider using the DirectoryEntry you already have bound to the WinNT provider. So, in the projects I've worked on, I generally prefer to get the SID of the current WinNT object and then use LDAP to search for domain objects with that same SID.
For Windows 2003+ domains you can convert your SID byte array to the usual SDDL format (S-1-5-21...) and then bind to an object with a matching SID using something like:
Byte[] SIDBytes = (Byte[])memberEntry.Properties["objectSID"].Value;
System.Security.Principal.SecurityIdentifier SID = new System.Security.Principal.SecurityIdentifier(SIDBytes, 0);
memberEntry.Dispose();
memberEntry = new DirectoryEntry("LDAP://" + _domainName + "/<SID=" + SID.ToString() + ">");
For Windows 2000 domains you can't bind directly to an object by SID. So, you have to convert your SID byte array to an array of the hex values with a "\" prefix (\01\06\05\16\EF\A2..) and then use the DirectorySearcher to find an object with a matching SID. A method to do this would look something like:
public DirectoryEntry FindMatchingSID(Byte[] SIDBytes, String Win2KDNSDomainName)
{
using (DirectorySearcher Searcher = new DirectorySearcher("LDAP://" + Win2KDNSDomainName))
{
System.Text.StringBuilder SIDByteString = new System.Text.StringBuilder(SIDBytes.Length * 3);
for (Int32 sidByteIndex = 0; sidByteIndex < SIDBytes.Length; sidByteIndex++)
SIDByteString.AppendFormat("\\{0:x2}", SIDBytes[sidByteIndex]);
Searcher.Filter = "(objectSid=" + SIDByteString.ToString() + ")";
SearchResult result = Searcher.FindOne();
if (result == null)
throw new Exception("Unable to find an object using \"" + Searcher.Filter + "\".");
else
return result.GetDirectoryEntry();
}
}
Is there any sample that deletes computer account from AD using C#?
I have searched many sources, but all are about user account.
added my code here, i always got errors for some reason.
public static bool checkExistingPC(string compName,string userName,string userPwd )
{
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://test.com",userName,userPwd,AuthenticationTypes.Secure);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=computer)(|(cn=" + compName + ")(dn=" + compName + ")))";
foreach (SearchResult result in mySearcher.FindAll())
{
if (result != null)
{
MessageBox.Show("computer GetDirectoryEntry():" + result.Path+"\n"+"computer path: "+result.Path);
DirectoryEntry entryToRemove = new DirectoryEntry(result.Path,userName,userPwd);
entry.Children.Remove(entryToRemove);
return true;
}
else
{
return false;
}
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
return false;
}
If you're on .NET 3.5 and up (if you're not - time to upgrade!), you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find the computer in question
ComputerPrincipal computer = ComputerPrincipal.FindByIdentity(ctx, "NAME");
// if found - delete it
if (computer != null)
{
computer.Delete();
}
The new S.DS.AM makes it really easy to play around with users, computers and groups in AD!
Using ADSI which is under System.DirectoryServices use a commit mechanism, here is a working sample :
/* Retreiving RootDSE infos
*/
string ldapBase = "LDAP://WM2008R2ENT:389/";
string sFromWhere = ldapBase + "rootDSE";
DirectoryEntry root = new DirectoryEntry(sFromWhere, "dom\\jpb", "PWD");
string defaultNamingContext = root.Properties["defaultNamingContext"][0].ToString();
/* Retreiving the computer to remove
*/
sFromWhere = ldapBase + defaultNamingContext;
DirectoryEntry deBase = new DirectoryEntry(sFromWhere, "dom\\jpb", ".biènèsph^r^.1966");
DirectorySearcher dsLookForDomain = new DirectorySearcher(deBase);
dsLookForDomain.Filter = "(&(cn=MACHSUPR))"; // MACHSUPR is the computer to delete
dsLookForDomain.SearchScope = SearchScope.Subtree;
dsLookForDomain.PropertiesToLoad.Add("cn");
dsLookForDomain.PropertiesToLoad.Add("distinguishedName");
SearchResultCollection srcComputer = dsLookForDomain.FindAll();
foreach (SearchResult aComputer in srcComputer)
{
/* For each computer
*/
DirectoryEntry computerToDel = aComputer.GetDirectoryEntry();
computerToDel.DeleteTree();
computerToDel.CommitChanges();
}
Use WMI and or System.DirectoryServices namespace (http://msdn.microsoft.com/en-us/library/system.directoryservices.aspx).
It may not be exactly what you are looking for, but this site provides a number of code examples for working with AD in C#, including deleting a security group and removing a user from a group
I have some C# code that's attempting to do an LDAP search using a supplied computer name to determine whether or not the computer account is disabled. Most of this code was taken from this SO question. The code in the example link works great and correctly displays true if I go disable an account in AD and false on computers that are active. The issue is that I can't exactly use the code in the manner it was originally presented, it must be used in the manner I have pasted it below. The problem with the below code is that it's always returning false, it doesn't seem to matter what computer name you pass to it. I also realize that the foreach loop is probably unneeded since I'm only trying to find one computer.
using System;
using System.DirectoryServices;
namespace DynamicNamespace
{
public class DynamicClass
{
public System.Boolean DynamicMethod(System.Boolean IsDisabled, System.String ComputerName)
{
//the string should be your a DC(domain controller)
const string ldap = "LDAP://server-name";
//DirectoryEntry is used for manipulating objects (users, computers)
DirectoryEntry entry = new DirectoryEntry(ldap);
//DirectorySearcher responds to a filter method for LDAP searches
//http://www.tek-tips.com/faqs.cfm?fid=5667 has a decent query guide
DirectorySearcher dSearch = new DirectorySearcher(entry);
//SAM Account Name was showing a $ sign at one point, using * for wildcard
dSearch.Filter = String.Format("samAccountName={0}*", ComputerName);
dSearch.PropertiesToLoad.Add("samAccountName");
dSearch.PropertiesToLoad.Add("userAccountControl");
SearchResultCollection results = dSearch.FindAll();
foreach (SearchResult result in results)
{
int userAccountControl = Convert.ToInt32(result.Properties["userAccountControl"][0]);
string samAccountName = Convert.ToString(result.Properties["samAccountName"][0]);
bool disabled = ((userAccountControl & 2) > 0);
if (disabled == false)
{
IsDisabled = false;
}
else
{
IsDisabled = true;
}
}
return IsDisabled;
}
}
}
You are probably receiving more than one SearchResult and since you're using a loop IsDisabled will be assigned multiple times.
According to the link in your comments, you're doing a partial match:
PARTIAL MATCH......................(attribute={partial value}*)
If the supplied computer name is exact, why not use:
EQUALITY...........................(attribute=value)
Then you can remove the loop:
dSearch.Filter = String.Format("(samAccountName={0})", ComputerName);
dSearch.PropertiesToLoad.Add("samAccountName");
dSearch.PropertiesToLoad.Add("userAccountControl");
SearchResult result = dSearch.FindOne();
bool disabled = (result != null) && ((userAccountControl & 2) > 0);
You should step through the debugger to confirm this, but its possible that if you are passing false in as the first argument when you call this function and the search is not getting any results, then your function will return the same false value that you passed in to begin with through IsDisabled.
There is nothing wrong with your code, the only problem is that you don't have a distinction between if the account does not exist and if it exists but is disabled.
You can do the following to detect if the account does not exists, it does not matter that you are doing a for loop because like you said it only does it once but if you like change it to below... (You have to change it to cater for the fact that it can return more than 1 results as well because you've got a * in your search filter)
SearchResultCollection results = dSearch.FindAll();
if (results.Count == 0)
throw new Exception("Account not found.");
else if (results.Count == 1)
{
SearchResult result = results[0];
int userAccountControl = Convert.ToInt32(result.Properties["userAccountControl"][0]);
string samAccountName = Convert.ToString(result.Properties["samAccountName"][0]);
bool disabled = ((userAccountControl & 2) > 0);
if (disabled == false)
{ IsDisabled = false; }
else { IsDisabled = true; }
}
else
throw new Exception("More than 1 result found, please filter");
try
{
bool res = dc.DynamicMethod(false, "Username");
}
catch (Exception ex)
{
if (ex.Message == "Account not found.")
{
//Do Something
}
else
throw ex;
}
Obviously you can replace throwing an exception with something more appropriate...
I have an application that checks to see if a user exists (if not create it) every time it starts. This is done as follows:
bool bUserExists = false;
DirectoryEntry dirEntryLocalMachine =
new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntries dirEntries = dirEntryLocalMachine.Children;
foreach (DirectoryEntry dirEntryUser in dirEntries)
{
bUserExists = dirEntryUser.Name.Equals("UserName",
StringComparison.CurrentCultureIgnoreCase);
if (bUserExists)
break;
}
The problem is on the majority of the systems where it is deployed. This can take 6 - 10 seconds, which is too long ... I need to find a way to reduce this (as much as possible). Is there a better or faster way I can use to verify if a user exists on the system or not?
I know there are other ways to solve this, like have the other applications sleep for 10 seconds, or have this tool send a message when it is ready, etc... But if I can greatly reduce the time it takes to find the user, it would make my life much easier.
.NET 3.5 supports new AD querying classes under the System.DirectoryServices.AccountManagement namespace.
To make use of it, you'll need to add "System.DirectoryServices.AccountManagement" as a reference AND add the using statement.
using System.DirectoryServices.AccountManagement;
using (PrincipalContext pc = new PrincipalContext(ContextType.Machine))
{
UserPrincipal up = UserPrincipal.FindByIdentity(
pc,
IdentityType.SamAccountName,
"UserName");
bool UserExists = (up != null);
}
< .NET 3.5
For versions of .NET prior to 3.5, here is a clean example I found on dotnet-snippets
DirectoryEntry dirEntryLocalMachine =
new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
bool UserExists =
dirEntryLocalMachine.Children.Find(userIdentity, "user") != null;
You want to use the DirectorySearcher.
Something like this:
static bool userexists( string strUserName ) {
string adsPath = string.Format( #"WinNT://{0}", System.Environment.MachineName );
using( DirectoryEntry de = new DirectoryEntry( adsPath ) ) {
try {
return de.Children.Find( strUserName ) != null;
} catch( Exception e ) {
return false;
}
}
}
That should be quicker. Also, you can reduce the properties if all you are doing is checking for existence.
Another way is the following (supports either local or domain user):
bool UserExists(string userName)
{
var user = new NTAccount(userName);
try
{
var sid = (SecurityIdentifier)user.Translate(typeof(SecurityIdentifier));
return true;
}
catch (IdentityNotMappedException)
{
return false;
}
}
User may be either unqualified, or qualified by machine/domain name (DOMAIN\UserName). If you need to specifically detect if the account exists on local machine, qualify it by Environment.MachineName ($"{Environment.MachineName}\\{userName}").
The following in a command prompt returns 1 if 'username' exists.
net user | find "username" /c