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.
I am trying to add over 100 users to a active directory group. But I have onlz their samaccountnames. I dont want to run a loop to find their DN I am trying to find if there is a way to add the users in bulk just with their samaccountnames.
string userSAm = string.Format("<sAMAccountName={0}>", samAccountName);
groupDirectoryEntry.Properties["member"].Add(userSAm);
groupDirectoryEntry.CommitChanges();
It is possible with SID though
string userSid = string.Format("<SID={0}>", sid);
groupDirectoryEntry.Properties["member"].Add(userSid);
groupDirectoryEntry.CommitChanges();
I have developed a small application which reads user information from Active directory.
In the beginning of the application I used the below filter
search.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(displayName=*" + username + "*))";
This worked fine.
Now, I am giving the user an option to retrieve the employee details based on username OR office OR title fields.
The query I used to get the details is as follows, but not working. It throws and exception
"search filter is invalid."
(&((&(objectCategory=Person)(objectClass=User)))(|((displayName=*" + username + "*)(l = *" + location + "*)(title=*" + title + "*))))";
Example: retieve the details of employee based on location : Hyderabad
The runtime query looks like this
(&((&(objectCategory=Person)(objectClass=User)))(|((displayName=**)(l = *hyder*)(title=**)))
search filter is invalid.
I think, as your intentions are not clear, what you are looking for is something more like:
(&(objectCategory=Person)(objectClass=User)(|(displayName=sam)(l=location)(title=title)))
Which could be visualized as:
(&
(objectCategory=Person)
(objectClass=User)
(|
(displayName=*sam*)
(l=*location*)
(title=*title*)
)
)
Of course you would need to put in your code parameters instead of the values shown.
I'm struggling a bit with my simple console-dump-program. I connect to AD using
DirectoryEntry entry =
new DirectoryEntry("LDAP://" + domain, username, password);
and from there I recursively loop thru every child by
foreach (DirectoryEntry child in entry.Children)
{
Traverse(child);
}
Then I start getting mambo jambo data, users popping up more then once and null objects so I wonder if the way I handle the AD that it is just a handle and not a copy so its not loaded completely when I start traversing it?
Any tips/pointers on what to do?
If you can, move up to .NET 3.5 and use the new System.DirectoryServices.AccountManagement namespace - much easier to use.
See: Managing Directory Security Principals in the .NET Framework 3.5
Also: you need to understand Active Directory isn't just a flat list of users and groups - it's a hierachical system of OU's (organizational units) that can be nested into one another, and can contain users, groups, computers and more.
So what exactly do you want to do?? Get the users for a given OU (e.g. "Sales")?? Or really get all users from your AD?? You do understand this could take quite a while, depending on the size of your company's AD.......
If you really want to get ALL users and ALL groups from your entire AD - you should probably set up a DirectorySearcher at the root level:
// set search root
DirectoryEntry deRoot = new DirectoryEntry("LDAP://dc=YourCompany,dc=com");
// declare directory searcher
DirectorySearcher dsUsers = new DirectorySearcher(deRoot);
// scope is full subtree, filter defines to search for users
dsUsers.SearchScope = SearchScope.SubTree;
dsUsers.Filter = "(objectCategory=person)";
// define what properties you want to have loaded into your search results
dsUsers.PropertiesToLoad.Add("givenName");
dsUsers.PropertiesToLoad.Add("surname");
dsUsers.PropertiesToLoad.Add("samAccountName");
// loop through results of search
foreach(SearchResult result in dsUsers.FindAll())
{
if(result.Properties["givenName"] != null)
string givenName = result.Properties["givenName"][0].ToString();
if(result.Properties["surname"] != null)
string surname = result.Properties["surname"][0].ToString();
if(result.Properties["sAMAccountName"] != null)
string samAccountName = result.Properties["sAMAccountName"][0].ToString();
}
When reading out the properties of your SearchResult, you need to check to make sure you did actually get a value back - otherwise your assignment will crash and burn....
For the groups, just use this filter instead:
dsUsers.Filter = "(objectCategory=group)";
If you can narrow your search, e.g. to a given OU, you can get much better performance, since the search tree gets smaller and thus the search would be a lot faster. To do so, just define a different LDAP path for your deRoot directory entry (e.g. LDAP://OU=Sales,DC=YourCOmpany,DC=com or whatever OU you want to search in).
Update: as I said - with .NET 3.5, it gets a lot easier still! You need to add a reference to System.DirectoryServices.AccountManagement, and then you can use code something like this using a sort of "query-by-example" approach:
// create a domain context for the current domain
PrincipalContext domain = new PrincipalContext(ContextType.Domain);
// create a principal object decsribing what to search for
UserPrincipal user = new UserPrincipal(domain);
user.IsActive = true;
// create a principal searcher for running a search operation
PrincipalSearcher searcher = new PrincipalSearcher(user);
// run the query
PrincipalSearchResult<Principal> results = searcher.FindAll();
// iterate over all results
foreach (Principal result in results)
{
Console.WriteLine("name: {0}", result.Name);
}
And for searching for groups, just instantiate a GroupPrincipal, set any properties on it and then pass that into the PrincipalSearcher to search for groups.
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);