We have a .NET Framework application used for changing user passwords. It uses the System.DirectoryServices.AccountManagement assembly.
Process goes as follows:
A user fills out a simple form (username, old pw, new pw, new pw repeated).
UserPrincipal.FindByIdentity() gets called to find the user
UserPrincipal.ChangePassword() gets called to change the password
Note: Principal context type is set to DOMAIN
Note2: everything works, the issue is with the user folders.
So apparently ChangePassword() creates a user profile in C:\Users folder in the application's machine and I cannot find any information why that happens. I would understand in the context was set to Machine, but in this case it's not.
There are over 6k folders now, one for each user, it's taking up a lot of space and slowing the machine down.
I tried recreating the problem locally and apparently the ChangePassword() creates a TEMP user profile in C:\Users in my computer, but then it disappears.
Code
Configuration
container.RegisterType<PasswordChangeService>(new InjectionFactory(m => new PasswordChangeService(
new PrincipalContext(ContextType.Domain, ConfigurationManager.AppSettings["LdapDomain"], ConfigurationManager.AppSettings["LdapQuery"]),
new LogService(new Persistence.LogDbContext())
)));
Code for finding user, changing password
using (var user = UserPrincipal.FindByIdentity(_principalContext, IdentityType.SamAccountName, passwordChange.Username)) {
if (user == null) {
Fail(passwordChange, "User not found");
}
user.ChangePassword(passwordChange.CurrentPassword, passwordChange.NewPassword);
LogSuccess(passwordChange);
}
Can anyone tell me why the user profiles get created?
How to fix this issue? Is this a configuration or permission problem?
(optional) I've seen examples where UserPrincipal.Save() gets called after, say, ChangePassword(), but it has always worked without it, so in what situation can it be used?
Thank you.
Update
after some unsuccessful searching I found out a few things that might be worth mentioning:
1. Someone from 2009 had the same problem technet link
The last comment "Since all the code snippets utilize the ChangePasword implementation of ADSI (which causes the profile generation) the simplest way to accomplish this programmatically would be to not use ADSI but System.DirectoryServices.Protocols instead and perform an attribute modification against unicodePwd".
2. When calling ChangePassword(), user directory was sometimes not created and at at those times Event Viewer showed a couple of errors
"Windows has backed up this user profile. Windows will automatically try to use the backup profile the next time this user logs on."
"Windows cannot find the local profile and is logging you on with a temporary profile. Changes you make to this profile will be lost when you log off."
3. Every "fix" I've seen resulted in fixing the consequence, but not the cause. I still have no idea why the profiles get created, but if the technet comment is legit, I guess I need to use another implementation.
I ended up using an example from SO link and this is my result:
string ldapPath = ConfigurationManager.AppSettings["LdapPath"];
string domainWithUserName = string.Format(ConfigurationManager.AppSettings["LdapDomain"], "\\", passwordChange.Username);
DirectoryEntry directoryEntry = new DirectoryEntry(ldapPath, domainWithUserName, passwordChange.CurrentPassword);
if (directoryEntry != null) {
DirectorySearcher search = new DirectorySearcher(directoryEntry);
search.Filter = "(SAMAccountName=" + passwordChange.Username + ")";
SearchResult result = search.FindOne();
if (result != null) {
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry == null) {
Fail(passwordChange, "User not found.");
}
userEntry.Invoke("ChangePassword", new object[] { passwordChange.CurrentPassword, passwordChange.NewPassword});
userEntry.CommitChanges();
LogSuccess(passwordChange);
}
}
I still need to narrow down the AD search to a specific OU, etc.
This implementation works, no user profile gets created, Event Viewer shows nothing. It's a shame, really, because I'd rather call a few methods instead of using 15 lines of code that do the same thing.
I am trying to learn how to create a new Windows group (using C#) and assign an AccessRule to it by using the local user/group directory services.
I have written the following code which is attempting to firstly create the group, obtain the DirectoryEntry for it, and then creating and assigning a new custom AccessRule:
using System;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Security.AccessControl;
...
...
var principalContext = new PrincipalContext(ContextType.Machine);
var group = GroupPrincipal.FindByIdentity(principalContext, "groupName");
if (group == null)
{
group = new GroupPrincipal(principalContext)
{
Name = "groupName",
GroupScope = GroupScope.Local,
Description = "groupName description",
SamAccountName = "groupName",
};
group.Save();
}
var path = $"WinNT://{Environment.MachineName}/groupName,group";
var directoryEntry = new DirectoryEntry(path);
var accessRule = new ActiveDirectoryAccessRule(
group.Sid,
ActiveDirectoryRights.WriteProperty,
AccessControlType.Allow,
PermissionsDataSource.CanOverrideExpiredKeysPermissionId,
ActiveDirectorySecurityInheritance.None);
directoryEntry.ObjectSecurity.AddAccessRule(accessRule);
directoryEntry.Options.SecurityMasks = SecurityMasks.Dacl;
directoryEntry.CommitChanges();
The line that is causing me problems at the moment is the following which attempts to add the newly created access rule to the security objects:
directoryEntry.ObjectSecurity.AddAccessRule(accessRule);
The ObjectSecurity property is null. Similarly, the Options property is null. I am therefore not convinced I am creating the GroupPrincipal correctly.
It would be amazing if someone with some experience or knowledge in this area could help me understand what I need to do to be able to add access rules to the group object like I am trying to do above.
Thanks in advance!
P.S. The value PermissionsDataSource.CanOverrideExpiredKeysPermissionId is simply an arbitrary Guid which relates to the specific unique permission mapping that the application I am writing uses when checking if the groups a user belongs to has an access rule with this value.
You're working with a local group. Local Windows groups don't have permissions.
You can see this by opening Computer Management (compmgmt.msc) -> Local Users and Groups -> Groups. Right-click on a group and click Properties. You'll see there is no Security tab.
Where can I find an example that does the following?
Pulls a user from Active Directory.
Gets the groups the user is a member of.
Gets a list of permissions assigned to each group.
This seems like a simple task but I can't find a solution.
The overall goal is to assign custom permissions and use them to control rights within an application.
If you're on .NET 3.5 and up, 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 a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// do something here....
}
// find the group in question
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "YourGroupNameHere");
// if found....
if (group != null)
{
// iterate over members
foreach (Principal p in group.GetMembers())
{
Console.WriteLine("{0}: {1}", p.StructuralObjectClass, p.DisplayName);
// do whatever you need to do to those members
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
The last point: permissions. Those aren't stored in Active Directory - and therefore, you can't retrieve those from any AD code.
Permissions are stored on the individual file system items, e.g. files and/or directories - or other objects (like registry keys, etc.). When you have an AD group or user account, you can read it's SID (Security Identifier) property - that SID will show up in ACL's (Access Control Lists) all over Windows - but from the user or group, there's no mechanism to get all permissions it might have anywhere in the machine/server.
Permissions for files and directories can e.g. be retrieved using the .GetAccessControl() method on the FileInfo and DirectoryInfo classes:
FileInfo info = new FileInfo(#"D:\test.txt");
FileSecurity fs = info.GetAccessControl();
DirectoryInfo dir = new DirectoryInfo(#"D:\test\");
DirectorySecurity ds = dir.GetAccessControl();
Deciphering and making sense of those is a whole different story altogether!
i am building a csharp application and i would like a dropdown list of all users in my outlook global address book (the same one when i click on To: from outlook gui. is this possible to get this progrmaticall? what are the security requirements here?
Security ramifications, in addition to the Outlook dependency left me unable to use this approach, in the past. As a result, I ended up building this in the form of an LDAP query. Another plus is that, (in response to your other question) you will be able to extract contact information because this information is stored in the Active Directory.
DISCLAIMER: It has been almost five years since I have looked at this code, so I'm afraid I no longer fully understand the query. Hopefully it's enough to get you started, however.
DirectoryEntry adFolderObject = new DirectoryEntry();
DirectorySearcher adSearcher = new DirectorySearcher(adFolderObject);
adSearcher.SearchScope = SearchScope.Subtree;
adSearcher.Filter = "(& (mailnickname=*) (| (&(objectCategory=person)(objectClass=user)(!(homeMDB=*))(!(msExchHomeServerName=*)))(&(objectCategory=person)(objectClass=user)(|(homeMDB=*)(msExchHomeServerName=*))) ))";
foreach (SearchResult adObject in adSearcher.FindAll())
{
Console.WriteLine("CN={0}, Path={1}", adObject.Properties["CN"][0], adObject.Path);
}
I have a C# application that scans a directory and gathers some information. I would like to display the account name for each file. I can do this on the local system by getting the SID for the FileInfo object, and then doing:
string GetNameFromSID( SecurityIdentifier sid )
{
NTAccount ntAccount = (NTAccount)sid.Translate( typeof( NTAccount ) );
return ntAccount.ToString();
}
However, this does not work for files on a network, presumably because the Translate() function only works with local user accounts. I thought maybe I could do an LDAP lookup on the SID, so I tried the following:
string GetNameFromSID( SecurityIdentifier sid )
{
string str = "LDAP://<SID=" + sid.Value + ">";
DirectoryEntry dirEntry = new DirectoryEntry( str );
return dirEntry.Name;
}
This seems like it will work, in that the access to "dirEntry.Name" hangs for a few seconds, as if it is going off and querying the network, but then it throws a System.Runtime.InteropServices.COMException
Does anyone know how I can get the account name of an arbitrary file or SID? I don't know much about networking or LDAP or anything. There's a class called DirectorySearcher that maybe I'm supposed to use, but it wants a domain name, and I don't know how to get that either - all I have is the path to the directory I'm scanning.
See here for a good answer:
The best way to resolve display username by SID?
The gist of it is this bit:
string sid="S-1-5-21-789336058-507921405-854245398-9938";
string account = new System.Security.Principal.SecurityIdentifier(sid).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
This approach works for me for non-local SID's over the active directory.
The SecurityReference object's Translate method does work on non-local SIDs but only for domain accounts. For accounts local to another machine or in a non-domain setup you would need to PInvoke the function LookupAccountSid specifying the specific machine name on which the look up needs to be performed.
System.DirectoryServices.AccountManagement.UserPrincipal class (msdn link) has a static function FindByIdentity to convert an SID to a User object. It should be able to work both against the local machine or an LDAP/Active Directory server. I have only used it against active directory.
Here is an example that I have used in IIS:
// Set the search context to a specific domain in active directory
var searchContext = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=SomeOU,DC=YourCompany,DC=com");
// get the currently logged in user from IIS
MembershipUser aspUser = Membership.GetUser();
// get the SID of the user (stored in the SecurityIdentifier class)
var sid = aspUser.ProviderUserKey as System.Security.Principal.SecurityIdentifier;
// get the ActiveDirectory user object using the SID (sid.Value returns the SID in string form)
var adUser = UserPrincipal.FindByIdentity(searchContext, IdentityType.Sid, sid.Value);
// do stuff to user, look up group membership, etc.
In C#, get the user SID and assign it to a string variable through:
string strUser = System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString();
You will need to use string because the ability to resolve to the UserName supports string. In other words, using var varUser will result in a namespace error.
string strUserName = new System.Security.Principal.SecurityIdentifier(strUser).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
You can also get account name of special accounts like "Everyone" with code like this that will work regardless of user's language settings:
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
string everyone = everyoneSid.Translate(typeof(System.Security.Principal.NTAccount)).ToString();
Ooh, then it's possible that the LDAP call is not working because you might not be in an Active Directory environment. If this is the case, then each of your machines is responsible for its own identity store. And your first code sample is not working across the network because the machine on which you are executing your code does not know how to resolve the SID that only makes sense on the remote machine.
You really should check if your machines are a part of an Active Directory. You would know this during the logon process. Or you can check by right clicking on "My Computer", select "Properties", the "Computer Name" tab, then see if your computer is part of a domain.
Great. I cribbed some LookupAccountSid() code from here:
http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid
And that worked, though I had to provide the host name myself. In the case of a UNC path I can just take the first component of it. When it's a mapped drive, I use this code to convert the path to a UNC one:
http://www.wiredprairie.us/blog/index.php/archives/22
It seems to work, so that's how I'll do it, unless someone comes up with a situation in which the first component of a UNC path isn't the host name...
Thank you all for your help.
This one is a stumper. You are in an Active Directory environment right? Just checking:)
Anyhow, instead of binding with sid.Value,
string str = "LDAP://<SID=" + sid.Value + ">";
I would try converting the SID's byte array to an Octet String and bind with that instead.
There is a sweet example here on page 78. This will get you closer. To be honest, I've not tried binding with a SID before. But I've had success binding with a user's GUID though :)
Good luck and let me know how it goes.
Get the current domain:
System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
Get a directory entry from ldap and the domain name:
DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://{0}", domain));
Get the sid from an ActiveDirectoryMembershipProvider ActiveDirectoryMembershipUser:
ActiveDirectoryMembershipUser user = (ActiveDirectoryMembershipUser)Membership.GetUser();
var sid = (SecurityIdentifier)user.ProviderUserKey;
Get the username from the SecurityIdentifier:
(NTAccount)sid.Translate(typeof(NTAccount));
Get directory search done on an activedirectory with the domain directory entry and username:
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = string.Format("(SAMAccountName={0})", username);
search.PropertiesToLoad.Add("Name");
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("company");
search.PropertiesToLoad.Add("homePhone");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("givenName");
search.PropertiesToLoad.Add("lastLogon");
search.PropertiesToLoad.Add("userPrincipalName");
search.PropertiesToLoad.Add("st");
search.PropertiesToLoad.Add("sn");
search.PropertiesToLoad.Add("telephoneNumber");
search.PropertiesToLoad.Add("postalCode");
SearchResult result = search.FindOne();
if (result != null)
{
foreach (string key in result.Properties.PropertyNames)
{
// Each property contains a collection of its own
// that may contain multiple values
foreach (Object propValue in result.Properties[key])
{
outputString += key + " = " + propValue + ".<br/>";
}
}
}
Depending on the data in your active directory, you will get a varied response in the output.
Here is a site that has all the user properties I needed:
For all the Windows developers, the answer is LookupAccountSid
LookupAccountSid(null, Sid, username, userSize, domainName, domainSize, sidType);
I am quite sure you will be able to use the accepted answer from here: Determine the LocalSystem account name using C#
Basically, you can translate an instance of the SecurityIdentifier class to type NTAccount, from which you can get the user name. In code:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);