Updating Name Field on UserPrincipal - c#

When I try to update the Name field (corresponds to the CN) on UserPrincipal (Principal, really), I get an error "The server is unwilling to process the request" on the call to UserPrincipal.Save().
I've checked to make sure there isn't another object in the same OU with the same Name (CN).
The PrincipalContext I'm operating at is the domain root (not exactly at the OU level where the user account exists).
What reason might there be for this error? Is it something that might be security policy related (even though I'm able to update all the other fields)?
using (var context = new PrincipalContext(ContextType.Domain, ConfigurationManager.AppSettings["domain"], ConfigurationManager.AppSettings["rootDN"], ContextOptions.Negotiate, ConfigurationManager.AppSettings["username"], ConfigurationManager.AppSettings["password"])) {
var user = UserPrincipal.FindByIdentity(context, IdentityType.Sid, "..."); // SID abbreviated
user.Name = "Name, Test";
user.Save();
}
The user I am using to create the PrincipalContext has the security rights to modify AD objects. If I update any other of the other fields (e.g. Surname, GivenName), everything works fine.
EDIT:
I've been able to accomplish what I need to do (using ADSI), but I have to run the following code under impersonation. The impersonation code is ugly, and the code below breaks away from the other way I'm updating AD data (using DirectoryServices.AccountManagement), so I'd like to get a better solution.
using (var companyOU = new DirectoryEntry("LDAP://" + company.UserAccountOU)) {
companyOU.Invoke("MoveHere", "LDAP://" + user.DistinguishedName, "cn=Name\, Test");
}

This is a cleaner way
using (var context = new PrincipalContext(ContextType.Domain))
{
var group = GroupPrincipal.FindByIdentity(context, groupName);
group.SamAccountName = newGroupName;
group.DisplayName = newGroupName;
group.Save();
var dirEntry = (DirectoryEntry)group.GetUnderlyingObject();
dirEntry.Rename("CN=" + newGroupName);
dirEntry.CommitChanges();
}

The only way I've found to do this is in the EDIT section in my question. Basically, you cannot use the UserPrincipal class. There is something special about the CN attribute, and you need to drop down a level and use DirectoryEntry, an LDAP string, and invoke the "MoveHere" ADSI command to rename the user account.

Related

How to Delete AD Group

Question :
How do I delete a group from active directory?
What I have tried:
1. PrinipalContext
I am trying to delete an active directory group. I have this right now:
using (var ctx = new PrincipalContext(ContextType.Domain, myDomain, ldapUser, ldapPassword))
{
var group1 = new GroupPrincipal(ctx, groupName);
group1.Delete();
}
But I get an error: "Unpersisted Principal objects can not be deleted."
That lead me here, but I don't know what the invoke magic is all about and it scares me a little bit.
2. DirectoryEntry
http://www.codeproject.com/Articles/18102/Howto-Almost-Everything-In-Active-Directory-via-C#33
But I just kept getting "The server is not operational" errors.
I just need to delete the AD group, is it even possible?
Turns out that the DirectoryEntry works fine, but my ldap urls were wrong. Here is the code that I ended up using:
using (var ou = new DirectoryEntry(ouPath, ldapUser, ldapPassword))
{
using (var group = new DirectoryEntry(groupPath, ldapUser, ldapPassword))
{
ou.Children.Remove(group);
group.CommitChanges();
}
}
WRONG OLD VALUES
ouPath LDAP://myDomain.local/OU=myTier1,DC=myDomain,DC=local
groupPath LDAP://groupname/OU=myTier3,OU=myTier2,OU=myTier1,DC=myDomain,DC=local
CORRECT NEW VALUES
ouPath LDAP://myDomain.local/OU=myTier2,OU=myTier1,DC=myDomain,DC=local
groupPath LDAP://myDomain.local/CN=groupName,OU=myTier3,OU=myTier2,OU=myTier1,DC=myDomain,DC=local

Setting flags for users (Active Directory)

at the moment I am trying to write some code to help our support and having some problems. I am creating a user in the OU, setting all needed attributes and having trouble setting the flags.
string ADPath1 = "LDAP://127.0.0.1/OU=TEST,DC=abc,DC=def,DC=local";
string ADUser = "admin";
string ADPassword = "somepw";
DirectoryEntry de = new DirectoryEntry(ADPath1, ADUser, ADPassword, AuthenticationTypes.Secure);
I am connecting, and setting everything up
DirectoryEntries users = de.Children;
DirectoryEntry newuser = users.Add("CN=" + "Artemij Voskobojnikov", "user");
newuser.Properties["property"].Add("XXX");
Now I'd like to setup the userAccountControl-propery and am trying to do the following:
const int UF_PASSWD_CANT_CHANGE = 0x0040;
const int UF_DONT_EXPIRE_PASSWD = 0x10000;
int user_flags = UF_PASSWD_CANT_CHANGE + UF_DONT_EXPIRE_PASSWD;
newuser.Properties["userAccountControl"].Value = user_flags
I am getting an error, something like "Server cannot execute the operation". Is there any way to do this or do I have to use UserPrincipal?
Kind regards
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 and bind to the OU=Test container
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=TEST"))
{
// create a new user
UserPrincipal user = new UserPrincipal(ctx);
// set first name, last name, display name, SAM Account Name and other properties easily
user.DisplayName = "Artemij Voskobojnikov";
user.GivenName = "Artemij";
user.Surname = "Voskobojnikov";
user.SamAccountName = "AVoskobojnikov";
// set some flags as appropriate for your use
user.UserCannotChangePassword = true;
user.PasswordNeverExpires = true;
// save user
user.Save();
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!

Active Directory DirectorySearcher is not returning all of the available properties

I am writting a new progam for my boss that will replace this old VBS that they are currently using.
So the program is suppose to go into the AD and collect the Name of all the employees and their email addresses. My problem is that each user has around 60ish properties assigned to them but my program is only pulling in 32 fields, one of which is the CN which is half of what I need. Of course mail is not one of the properties being imported. I have also noticed while debugging that I think is is only bring in the emlpoyees from the Long Island branches and not from everywhere which I dont understand why. Any help would be greatly appreciated!! =D
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using Microsoft.Office.Interop.Excel;
using System.DirectoryServices.ActiveDirectory;
namespace EmailListing
{
class Program
{
static void Main(string[] args)
{
DirectoryEntry adFolderObject = new DirectoryEntry("LDAP://OU=PHF Users,DC=phf,DC=inc");
DirectorySearcher adSearchObject = new DirectorySearcher(adFolderObject);
adSearchObject.SearchScope = SearchScope.Subtree;
adSearchObject.Filter = "(&(ObjectClass=user)(!description=Built-in*))";
foreach (SearchResult adObject in adSearchObject.FindAll())
{
//mail = adObject.Properties["mail"].ToString();
Console.Write(adObject.Properties["cn"][0]);
Console.Write(". ");
//Console.WriteLine(mail);
}
Console.WriteLine();
Console.ReadLine();
}
}
}
You can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// define a "query-by-example" principal - here, we search for a UserPrincipal
UserPrincipal qbeUser = new UserPrincipal(ctx);
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here - "found" is of type "Principal" - it could be user, group, computer.....
UserPrincipal foundUser = found as UserPrincipal;
if (foundUser != null && !foundUser.Description.StartsWith("Built-In"))
{
string firstName = foundUser.GivenName;
string lastName = foundUser.Surname;
string email = foundUser.EmailAddress;
}
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement. Or see the MSDN documentation on the System.DirectoryServices.AccountManagement namespace.
Of course, depending on your need, you might want to specify other properties on that "query-by-example" user principal you create:
DisplayName (typically: first name + space + last name)
SAM Account Name - your Windows/AD account name
User Principal Name - your "username#yourcompany.com" style name
You can specify any of the properties on the UserPrincipal and use those as "query-by-example" for your PrincipalSearcher.

Domain Users group is not getting for an active directory user

We are using the following code to get the groups of an active directory user.
StringCollection groups = new StringCollection();
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domainName, userName, password))
{
//find user roles
UserPrincipal user = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, loginUserName);
if (user != null)
{
DirectoryEntry de = (DirectoryEntry)user.GetUnderlyingObject();
object obGroups = de.Invoke("Groups");
foreach (object ob in (IEnumerable)obGroups)
{
DirectoryEntry obGpEntry = new DirectoryEntry(ob);
groups.Add(obGpEntry.Name);
}
}
}
}
catch (Exception e)
{
}
This is working almost as expected. But while we checking the users with Domain Users group, the method didn't return the group name. Some users are only with this Domain Users group and while we calling this method for such users its returning an empty group.
Any suggestions please..
It's a well-known and documented "omission" that the so called primary group is not returned from the Groups method in this code. There are some rather cryptic ways around this - or try this other approach:
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)
{
// the call to .GetAuthorizationGroups() will return **all** groups that
// user is a member of - including the primary group and all nested
// group memberships, too!
var result = user.GetAuthorizationGroups();
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
Update: if you insist on using the old legacy technology, check out this blog post by Ryan Dunn which explains in great detail how to get the primary group for an AD account in C#.

I have a SID of a user account, and I want the SIDs of the groups it belongs to

This has to be obtained from a remote machine. The following query works not for SIDs, but for group and account names.
"SELECT GroupComponent FROM Win32_GroupUser WHERE PartComponent = \"Win32_UserAccount.Domain='" + accountDomain + "',Name='" + accountName + "'\""
The Win32_Group objects it returns come in the forms of strings, and they only have domain and name (even though Win32_Group has a SID property).
I have this sinking feeling I'll have to:
Turn the SID into an account name by querying Win32_SID;
Perform the query above;
Turn each of the resulting group names into SIDs by querying Win32_Group.
Can you use the System.DirectoryServices.AccountManagement namespace classes?
using (var context = new PrincipalContext( ContextType.Domain ))
{
using (var user = UserPrincipal.FindByIdentity( context, accountName ))
{
var groups = user.GetAuthorizationGroups();
...iterate through groups and find SIDs for each one
}
}
It should work with ContextType.Machine, though you'd need to specify the machine name and have appropriate privileges.
using (var context = new PrincipalContext( ContextType.Machine,
"MyComputer",
userid,
password ))
{
...
}
There's a nice MSDN article (longish, though) on using the new .NET 3.5 account management namespace.
Yes there is but some methods depend on having a domain.
See this page for how to convert a SID
to a user id using P/Invoke and the Windows API, or with .NET 2.0+ and no P/Invoke.
using System.Security.Principal;
// convert the user sid to a domain\name
string account = new SecurityIdentifier(stringSid).Translate(typeof(NTAccount)).ToString();
If you have AD and
the user id in there then use the DirectorySearcher
method or Account Management APIs to find the groups.
Otherwise use the method outlined in
this article to get local
groups.
Now use the API suggested by #tvanfosson to iterate the groups and get the SIDs. Or follow the info below.
In an ASP.NET application it is possible to use code like this to access group info provided a user is authenticated by Windows and not Forms authentication. In this example I've left an interesting note about exceptions that are thrown in that environment but it may apply to other users:
public List<string> GetGroupsFromLogonUserIdentity()
{
List<string> groups = new List<string>();
HttpRequest request = HttpContext.Current.Request;
if (request.LogonUserIdentity.Groups != null)
{
foreach (IdentityReference group in request.LogonUserIdentity.Groups)
{
try
{
groups.Add(group.Translate(typeof(NTAccount)).ToString());
}
catch (IdentityNotMappedException)
{
// Swallow these exceptions without throwing an error. They are
// the result of dead objects in AD which are associated with
// user accounts. In this application users may have a group
// name associated with their AD profile which cannot be
// resolved in the Active Directory.
}
}
}
return groups;
}
LogonUserIdentity is based on the WindowsIdentity class. You could modify my code sample to use WindowsIdentity and function in a non-Web application. Once you iterate over a group you should be able to do something like this to get the SecurityIdentifier:
SecurityIdentifier secid = group as SecurityIdentifier;

Categories