Modifying Active Directory Properties - c#

I am trying to modify certain properties for users in our active directory. Some properties I can change and some I can't. I am doing impersonation, but for some of the properties I still get the "General Access denied error" when I try to call CommitChanges().
For example this will work:
DirectoryEntry deUser = new DirectoryEntry(result.Path);
if (ImpersonateValidUser(adConnectionUsername, adConnectionDomain, adConnectionPassword))
{
deUser.Properties["ampPasswordQuestion"].Value = newPasswordQuestion;
deUser.Properties["ampPasswordAnswer"].Value = newPasswordAnswer;
deUser.CommitChanges();
deUser.Close();
UndoImpersonation();
}
This works presumably because we've manually added the properties into the AD schema with no access restrictions. (See ASP.NET 3.5 Security, Membership, and Role Management with C# and VB)
However if I try to modify something like the Comment property of a record as follows:
DirectoryEntry deUser = new DirectoryEntry(result.Path);
if (ImpersonateValidUser(adConnectionUsername, adConnectionDomain, adConnectionPassword))
{
deUser.Properties["comment"].Value = comment;
deUser.CommitChanges();
deUser.Close();
UndoImpersonation();
}
Then I will get "General access denied error".
Anyone have any ideas?
The Impersionation code is from Microsoft found at: http://support.microsoft.com/kb/306158

I believe I have found the problem. Apparently you need to create the DirectoryEntry that will be modified after Impersonation is done. This seems to make sense because I believe the rights of the modifying user become effective at the creation of the entry to be modified. Can anyone verify this? I have not seen it explained this way in any documentation.
I did some more testing and found out that the first entry does not even require Impersonation because the those attributes are not secured.
In the 2nd case the following code works:
if (ImpersonateValidUser(adConnectionUsername, adConnectionDomain, adConnectionPassword))
{
DirectoryEntry deUser = new DirectoryEntry(result.Path);
deUser.Properties["comment"].Value = comment;
deUser.CommitChanges();
deUser.Close();
UndoImpersonation();
}

Related

How to create GMSA account via C#

I have tried to look for the c# code example to see how the AD service account is created but not much luck. Anyone can provide an example code for creating AD service account please?
I have tried UserPrincipal with $ at the end of the name but not much luck. Errors with Access Denied (Cant create under root MyDomain or under a CN)
// Domain Context to use specific LDAP path.
domainContext = new PrincipalContext(ContextType.Domain, domainContext.ConnectedServer, "CN=Managed Service Accounts,dc=mydomain");
UserPrincipal userAccount = new UserPrincipal(domainContext)
{
DisplayName = userName,
SamAccountName = $"{userName}$",
Description = description
};
userAccount.Save();
Little late, but I think I can answer it. You need to use the NetAddServiceAccount function through logoncli.dll. I hadn't been able to get it to work in PowerShell, even with adding what I thought was an appropriate type shim, but I just came across a module that seems to work.
https://github.com/beatcracker/Powershell-Misc/blob/master/Use-ServiceAccount.ps1
The C# code for the type definition in that script should have everything you need to implement it for yourself.

LDAP Path And Permissions To Query Local User Directory?

I am working on a web application, ASP.NET, C#. Users are required to log in using an account local to the machine the app is running on, which I'll call "cyclops" for this example. I want the app to be able to query the local directory of users and groups to determine what groups the user is in. The code looks something like this:
DirectoryEntry entry = new DirectoryEntry("WinNT://cyclops/Users", "SomeServiceAccount",
"SvcAcctP#$$word", AuthenticationTypes.Secure);
entry.RefreshCache();
// Etc.
My two problems are:
That's pretty clearly not the correct path to use, but my research
and experimentation hasn't found the right answer. This MSDN
article talks about local paths, but doesn't fill in the blanks.
Do I use "LDAP://cyclops/Users", "WinNT://localhost/Users",
"WinNT://cyclops/cn=Users"?
As you can see, I'm providing the
credentials of a local service account. That account needs
permission to access the local directory, but I have no idea where
to set those permissions. Is it a specific file somewhere? Does
the account need to be a member of a particular group?
My experimentation has produced many errors: "The group name could not be found.", "The provider does not support searching...", "The server is not operational.", "Unknown error (0x80005004)", etc.
Thank you for your time...
-JW
WinNT requires the following format
WinNT://<domain/server>/<object name>,<object class>
To get groups of a given user, use
using (DirectoryEntry user = new DirectoryEntry("WinNT://./UserAccount,user"))
{
foreach(object group in (IEnumerable)user.Invoke("Groups",null))
{
using(DirectoryEntry g = new DirectoryEntry(group))
{
Response.Write(g.Name);
}
}
}
where
UserAccount is a name of required user.
dot stands for current machine (you can replace it with cyclops or use Environment.MachineName)
user credentials ("SomeServiceAccount", "SvcAcctP#$$word") might be required, depends on setup
To get users in a particular group, use
using (DirectoryEntry entry = new DirectoryEntry("WinNT://./Users,group"))
{
foreach (object member in (IEnumerable)entry.Invoke("Members"))
{
using(DirectoryEntry m = new DirectoryEntry(member))
{
Response.Write(m.Name);
}
}
}
where
Users is a name of group

Active Directory property "badPwdCount"

Problem:
We've upgraded the AD server from 2003 to 2008 and due to some "bad code", where developer has coded in such a way that, he directly casts "badPwdCount" property value to INT and it blows up because of NULL value conversion - NULL reference exception - NULL cannot be converted to INT.
Bigger problem:
We cannot do a deployment at this point because there are over 100 individual apps that depended on this change and we're looking for a least involved way of dealing with it for now.
Background:
Now the way this "badPwdCount" property works is, that when user logs on to the domain, it will get set to zero, otherwise it's NULL. The problem is that none of these users are ever going to log on interactively because they're external and we authenticate them via API and they cannot log in using the API either..
Question:
Does anyone know if this value is in the registry or somewhere, where I can get to it and set it to zero? Was also thinking of initiating a log in per user via a script, but wanted to gather other ideas too...
MSDN page for badPwdCount:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms675244(v=vs.85).aspx
Normally this would be easy, all you would need to do is update all the users in active directory and set the value to 0 if it is null. There are various ways you could do this, for example a script or code, or a bulk update tool.
In this case, badPwdCount is a special property that is not replicated (i.e. it is different for each domain controller) and so far as I can tell, there is no way to update it manually or by script, however, I think I have a solution for you.
You should be able to easily trigger a single failed login for every user in active directory against each domain controller, causing the value to be incremented.
Since you tagged your post with C#, here is some C# code that will do the trick for you:
using System.DirectoryServices.AccountManagement;
using System.DirectoryServices.ActiveDirectory;
...
using (Domain domain = Domain.GetComputerDomain())
{
foreach (DomainController domainController in domain.DomainControllers)
{
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, domainController.Name))
using (UserPrincipal userPrincipal = new UserPrincipal(context))
using (PrincipalSearcher searcher = new PrincipalSearcher(userPrincipal))
using (PrincipalSearchResult<Principal> results = searcher.FindAll())
{
foreach (UserPrincipal user in results.OfType<UserPrincipal>())
{
context.ValidateCredentials(user.SamAccountName, "THEREISNOWAYTHISISTHECORRECTPASSWORD");
}
}
}
}
PS. If this screws up your AD I take no responsibility for it!

Setting user password using C# does not work, any ideas?

I need to reset a user password. to do so, I use the following code:
DirectoryEntry de = ..
de.AuthenticationType = AuthenticationType.Secure
de.Password = txtPassword.text
de.CommitChanges()
When i run the code - nothing happens. The user password does not change, and no exception is thrown.
if i use the following method:
de.Invoke("SetPassword", .. );
when i run the code, I get a message that says: Please insert smartcard ...
I have admin privilages over the user account.
The user does not have UAV set for smart card.
Any ideas ?
The Password property of the DirectoryEntry class isn't what you think it is. You're not changing the user's password, you're changing the password you're using to access further information from the DirectoryEntry object.
From the MSDN documentation:
You can set the Username and Password properties to specify alternate
credentials with which to access the information in Active Directory
Domain Services. Any other DirectoryEntry objects retrieved from this
instance (for example, through Children) are automatically created
with the same alternate credentials.
With your second method, if you're being asked to insert a smartcard I doubt that has anything to do with the user you're modifying - it's more likely it's asking for your smartcard. If you're not set up to use smartcards either, then I'm really not sure why it's asking you for one at all.
Take a look at this related question and see if the answers there help.

How do I detect if my program runs in an Active Directory environment?

How do I detect if my program runs in an Active Directory environment?
I'm using C# and .Net 2.0
Try getting Environment.UserDomainName and comparing it to Environment.MachineName. If the two are the same then it's likely that the user does not have a domain. If they are not the same then the user is logged into a domain which must have a directory server.
This code will check if the Computer itself is a member of a domain
using System.DirectoryServices.ActiveDirectory;
bool isDomain = false;
try
{
Domain.GetComputerDomain();
isDomain = true;
}
catch (ActiveDirectoryObjectNotFoundException)
{
}
However the computer can be in a domain, but the currently logged in user may be a local user account. If you want to check for this use the Domain.GetCurrentDomain() function
One way might be to query the LOGONSERVER environmental variable. That'll give the server name of your AD controller... Which, as far as I know, will be blank (or match current workstation? Not sure) if it isn't currently logged into a domain.
Example Usage:
string ADServer = Environment.GetEnvironmentVariable("LOGONSERVER");
I found something that works:
using System.Net.NetworkInformation;
IPGlobalProperties.GetIPGlobalProperties().DomainName;
Works with a local user and a domain user.
From http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.path.aspx
To bind to the current domain using LDAP, use the path "LDAP://RootDSE", then get the default naming context and rebind the entry.
So without a domain the binding to "LDAP://RootDSE" should either fail or return nothing. I didn't try it for myself.
use System.DirectoryServices; // add reference to system.directoryservices.dll
...
DirectoryEntry ent = new DirectoryEntry("LDAP://RootDSE");
String str = ent.Properties["defaultNamingContext"][0];
DirectoryEntry domain = new DirectoryEntry("LDAP://" + str);
This is definitely a cleaner way of checking for an Active Directory than relying on an environment variable (which the user could delete or add to spoof the program).

Categories