How can I create a local user account using .NET 2.0 and c# and also be able to set the "Password never expires" to never.
I have tried using "Net.exe" using Process.Start and passing its parameters but it seems that the "net user" is unable to set the "Password never expires" to never.
This code will create a local account with the password never expires option set:
using System.DirectoryServices;
DirectoryEntry hostMachineDirectory = new DirectoryEntry("WinNT://localhost");
DirectoryEntries entries = hostMachineDirectory.Children;
bool userExists = false;
foreach (DirectoryEntry each in entries)
{
userExists = each.Name.Equals("NewUser",
StringComparison.CurrentCultureIgnoreCase);
if (systemtestUserExists)
break;
}
if (false == userExists)
{
DirectoryEntry obUser = entries.Add("NewUser", "User");
obUser.Properties["FullName"].Add("Local user");
obUser.Invoke("SetPassword", "abcdefg12345#");
obUser.Invoke("Put", new object[] {"UserFlags", 0x10000});
obUser.CommitChanges();
}
The 0x10000 flag means PasswordNeverExpires.
I spent a long time figuring out how to create a local user account with the password set not to expire. It seems that when you try to use:
int val = (int)newUser.Properties["userAccountControl"].Value;
newUser.Properties["userAccountControl"].Value = val | 0x10000
permissions from active directory come into play. If you have active directory permissions everything works fine. If you don't then getting the userAccountControl property will always result in a null value. Trying to set userAccountControl will result in an exception "The directory property cannot be found in the cache".
However after much hunting around I found another property "UserFlags" that needs to be set using Invoke. You can use this to set the flag on a local account. I've tried this code and it worked on windows server 2008.
Hope this helps
Read this excellent CodeProject article
Howto: (Almost) Everything In Active Directory via C#
There is a section "Create User Account" and "Dealing with User Passwords".
UPDATE:
To adapt the code for local accounts replace the respective lines with these:
DirectoryEntry localMachine = new DirectoryEntry("WinNT://" +
Environment.MachineName);
DirectoryEntry newUser = localMachine.Children.Add("localuser", "user");
Here starts the original code snippet for domain accounts:
public string CreateUserAccount(string ldapPath, string userName,
string userPassword)
{
string oGUID = string.Empty;
try
{
string connectionPrefix = "LDAP://" + ldapPath;
DirectoryEntry dirEntry = new DirectoryEntry(connectionPrefix);
DirectoryEntry newUser = dirEntry.Children.Add
("CN=" + userName, "user");
newUser.Properties["samAccountName"].Value = userName;
int val = (int)newUser.Properties["userAccountControl"].Value;
newUser.Properties["userAccountControl"].Value = val | 0x10000;
newUser.CommitChanges();
oGUID = newUser.Guid.ToString();
newUser.Invoke("SetPassword", new object[] { userPassword });
newUser.CommitChanges();
dirEntry.Close();
newUser.Close();
}
catch (System.DirectoryServices.DirectoryServicesCOMException E)
{
//DoSomethingwith --> E.Message.ToString();
}
return oGUID;
}
There are some specifics to understand
when dealing with user passwords and
boundaries around passwords such as
forcing a user to change their
password on the next logon, denying
the user the right to change their own
passwords, setting passwords to never
expire, to when to expire, and these
tasks can be accomplished using
UserAccountControl flags that are
demonstrated in the proceeding
sections.
Please refer to this great
MSDN article: Managing User Passwords
for examples and documentation
regarding these features.
CONST HEX
------------------------------------------
SCRIPT 0x0001
ACCOUNTDISABLE 0x0002
HOMEDIR_REQUIRED 0x0008
LOCKOUT 0x0010
PASSWD_NOTREQD 0x0020
PASSWD_CANT_CHANGE 0x0040
ENCRYPTED_TEXT_PWD_ALLOWED 0x0080
TEMP_DUPLICATE_ACCOUNT 0x0100
NORMAL_ACCOUNT 0x0200
INTERDOMAIN_TRUST_ACCOUNT 0x0800
WORKSTATION_TRUST_ACCOUNT 0x1000
SERVER_TRUST_ACCOUNT 0x2000
DONT_EXPIRE_PASSWORD 0x10000
MNS_LOGON_ACCOUNT 0x20000
SMARTCARD_REQUIRED 0x40000
TRUSTED_FOR_DELEGATION 0x80000
NOT_DELEGATED 0x100000
USE_DES_KEY_ONLY 0x200000
DONT_REQ_PREAUTH 0x400000
PASSWORD_EXPIRED 0x800000
TRUSTED_TO_AUTH_FOR_DELEGATION 0x1000000
using System.DirectoryServices;
DirectoryEntry hostMachineDirectory = new DirectoryEntry("WinNT://localhost");
DirectoryEntries entries = hostMachineDirectory.Children;
bool userExists = false;
foreach (DirectoryEntry each in entries)
{
userExists = each.Name.Equals("NewUser",
StringComparison.CurrentCultureIgnoreCase);
if (systemtestUserExists)
break;
}
if (false == userExists)
{
DirectoryEntry obUser = entries.Add("NewUser", "User");
obUser.Properties["FullName"].Add("Local user");
obUser.Invoke("SetPassword", "abcdefg12345#");
obUser.Invoke("Put", new object[] {"UserFlags", 0x10000});
obUser.CommitChanges();
Related
Background
I've been experimenting with active directory access in C# to find out how to connect/validate credentials in various ways. At the bottom of this answer I've included some code snippets to give an idea of what I've done, maybe this can be built upon to fulfill my aim.
Main Aim
If I have valid credentials for connecting to an Active Directory, can I take an string representing a username/email address (assuming it exists in the userPrincipalName or similar field), and get back the objectGUID?
Or do I need to take other things into account like: the permissions those credentials have to search other users; knowledge of the structure of different ADs; if userPrincipalName is the correct field to search?
Code Snippets (experimental beginnings, not fully functional for my aim)
var credentials = new NetworkCredential(username, password, hostname);
var serverId = new LdapDirectoryIdentifier(hostname);
var connection = new LdapConnection(serverId, credentials);
try
{
connection.Bind();
}
catch (Exception e)
{
//error
Console.WriteLine(e);
connection.Dispose();
return;
}
//success
var dirEntry = new DirectoryEntry(string.Format("LDAP://{0}/{1}", hostname, baseDn), username, password);
var searcher = new DirectorySearcher(dirEntry)
{
Filter = "(&(&(objectClass=user)(objectClass=person)))"
};
var resultCollection = searcher.FindAll();
searcher.Dispose();
You're on the right track with DirectorySeacher. You just need a proper query, and a few other tweaks.
Modify the Filter so you find what you're looking for.
a. If you have the email address:
(&(objectClass=user)(objectClass=person)(mail=email#example.com))
Or, (&(objectClass=user)(objectClass=person)(proxyAddresses=smtp:email#example.com)) (this will match against secondary email addresses too)
b. If you have a username, it depends which username you have.
User Principal Name: (&(objectClass=user)(objectClass=person)(userPrincipalName=username#example.com))
What is normally called the "username", which is often formatted like DOMAIN\username: (&(objectClass=user)(objectClass=person)(sAMAccountName=myusername))
Use DirectorySeacher.PropertiesToLoad. If you don't, it will retrieve every attribute that has a value, which is just wasted network traffic.
You don't need to dispose the DirectorySearcher, but you do need to dispose resultCollection since the documentation says you can end up with a memory leak if you leave it up to garbage collection.
So, assuming you have the userPrincipalName, you would have something like this:
var userToLookFor = "username#example.com";
var dirEntry = new DirectoryEntry(string.Format("LDAP://{0}/{1}", hostname, baseDn), username, password);
var searcher = new DirectorySearcher(dirEntry)
{
Filter = $"(&(objectClass=user)(objectClass=person)(userPrincipalName={userToLookFor}))",
SizeLimit = 1 //we're only looking for one account
};
searcher.PropertiesToLoad.Add("objectGuid");
using (var resultCollection = searcher.FindAll())
{
if (resultCollection.Count == 1)
{
var userGuid = new Guid((byte[]) resultCollection[0].Properties["objectGuid"][0]);
}
else
{
//the account was not found - do something else
}
}
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 searched the site for information and found this:
ASP.NET C# Active Directory - See how long before a user's password expires
which explains how to get the value of when the password expires as per Domain Policy.
My question is this: what if the user has an OU Group Policy that has a different MaxPasswordAge value, overriding the one specified in Domain Group Policy? How to programatically get the OU's Group Policy Object?
Edit: To make this question a little bit more clear, I am adding this edit. What I am after is to being able to tell when user's password expires. As far as I understand that date value can either be governed by domains local policy or by group object policy. I have a Linq2DirectoryService Provider that translates Linq to Ldap queries. So an LDAP query to get the date expiration value would be optimal for this subj. If you answer includes what objects wrappers supported by .net are included into this equation - it would be a dead on answer!
Let me start with http://support.microsoft.com/kb/323750 which contains Visual Basic and VBScript examples and http://www.anitkb.com/2010/03/how-to-implement-active-directory.html which outlines how the maxPwdAge OU setting impacts computers, not users. It also has a comment pointing to AloInfo.exe as a tool from MS that can be used to get password ages.
Here is the example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
namespace LDAP
{
class Program
{
static void Main(string[] args)
{
string domainAndUsername = string.Empty;
string domain = string.Empty;
string userName = string.Empty;
string passWord = string.Empty;
AuthenticationTypes at = AuthenticationTypes.Anonymous;
StringBuilder sb = new StringBuilder();
domain = #"LDAP://w.x.y.z";
domainAndUsername = #"LDAP://w.x.y.z/cn=Lawrence E."+
" Smithmier\, Jr.,cn=Users,dc=corp,"+
"dc=productiveedge,dc=com";
userName = "Administrator";
passWord = "xxxpasswordxxx";
at = AuthenticationTypes.Secure;
DirectoryEntry entry = new DirectoryEntry(
domain, userName, passWord, at);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
SearchResultCollection results;
string filter = "maxPwdAge=*";
mySearcher.Filter = filter;
results = mySearcher.FindAll();
long maxDays = 0;
if(results.Count>=1)
{
Int64 maxPwdAge=(Int64)results[0].Properties["maxPwdAge"][0];
maxDays = maxPwdAge/-864000000000;
}
DirectoryEntry entryUser = new DirectoryEntry(
domainAndUsername, userName, passWord, at);
mySearcher = new DirectorySearcher(entryUser);
results = mySearcher.FindAll();
long daysLeft=0;
if (results.Count >= 1)
{
var lastChanged = results[0].Properties["pwdLastSet"][0];
daysLeft = maxDays - DateTime.Today.Subtract(
DateTime.FromFileTime((long)lastChanged)).Days;
}
Console.WriteLine(
String.Format("You must change your password within"+
" {0} days"
, daysLeft));
Console.ReadLine();
}
}
}
The following code worked for me to get the password expiration date on both domain and local user accounts:
public static DateTime GetPasswordExpirationDate(string userId, string domainOrMachineName)
{
using (var userEntry = new DirectoryEntry("WinNT://" + domainOrMachineName + '/' + userId + ",user"))
{
return (DateTime)userEntry.InvokeGet("PasswordExpirationDate");
}
}
Use following method to get expiration date of the account-
public static DateTime GetPasswordExpirationDate(string userId)
{
string forestGc = String.Format("GC://{0}", Forest.GetCurrentForest().Name);
var searcher = new DirectorySearcher();
searcher = new DirectorySearcher(new DirectoryEntry(forestGc));
searcher.Filter = "(sAMAccountName=" + userId + ")";
var results = searcher.FindOne().GetDirectoryEntry();
return (DateTime)results.InvokeGet("PasswordExpirationDate");
}
Some of the previous answers rely on the DirectoryEntry.InvokeGet method, which MS says should not be used. So here's another approach:
public static DateTime GetPasswordExpirationDate(UserPrincipal user)
{
DirectoryEntry deUser = (DirectoryEntry)user.GetUnderlyingObject();
ActiveDs.IADsUser nativeDeUser = (ActiveDs.IADsUser)deUser.NativeObject;
return nativeDeUser.PasswordExpirationDate;
}
You'll need to add a reference to the ActiveDS COM library typically found at C:\Windows\System32\activeds.tlb.
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