I am making a webpart that needs to fetch the current user information from active directory using below code:
protected void fetchUserInfo()
{
System.Security.PermissionSet ps = new System.Security.PermissionSet(System.Security.Permissions.PermissionState.Unrestricted);
ps.Assert();
Microsoft.SharePoint.SPServiceContext serviceContext = Microsoft.SharePoint.SPServiceContext.Current;
UserProfileManager upm = new Microsoft.Office.Server.UserProfiles.UserProfileManager(serviceContext);
ProfileSubtypePropertyManager pspm = upm.DefaultProfileSubtypeProperties;
string userName = SPContext.Current.Web.CurrentUser.LoginName;
UserProfile profile = upm.GetUserProfile(userName);
foreach (ProfileSubtypeProperty prop in pspm.PropertiesWithSection)
{
}
}
However an InvalidProgramException throws on line ProfileSubtypePropertyManager pspm = upm.DefaultProfileSubtypeProperties;
The error message is:
Common Language Runtime detected an invalid program.
I tried googling
Common Language Runtime detected an invalid program. sharepointUserProfileManager but there aren't much info.
What could be the problem?
Just now saw that the exception flows on UserProfileManager, so the SPServiceContext isn't valid, and when I look at the property SiteSubscriptionId on serviceContext, I found it was 00000000-0000-0000-0000-000000000000
So what does it mean? is there any alternative way to get the current user info from sharepoint?
At last, I changed my mind using UserPrincipal to get the user info:
PrincipalContext _principalcontext = GetPrincipalContext();
UserPrincipal _user = UserPrincipal.FindByIdentity(_principalcontext, IdentityType.SamAccountName, UID);
if (_user != null)
{
DirectoryEntry directoryEntry = _user.GetUnderlyingObject() as DirectoryEntry;
//Property is here;
}
Related
I am trying to set manager, department and title property of a user in active directory using C#.
Below is my code.
using (var context = new PrincipalContext(ContextType.Domain, "DomainName", "UserName", "Password"))
{
using (var userPrincipal = new UserPrincipal(context))
{
userPrincipal.SamAccountName = "UserSamACcountName";
using (PrincipalSearcher search = new PrincipalSearcher(userPrincipal))
{
UserPrincipal result = (UserPrincipal)search.FindOne();
DirectoryEntry directoryEntry = result.GetUnderlyingObject() as DirectoryEntry;
directoryEntry.Properties["manager"].Value = "<Manager Name>";
directoryEntry.Properties["title"].Value = "<Designation>";
directoryEntry.Properties["department"].Value = "<Department>";
directoryEntry.CommitChanges();
}
}
}
But I am getting below error when committing the changes.
A constraint violation occurred.
After debugging I found out that these properties (manager,title,department) are not available in DirectoryEntry properties collection. I could set "mailNickName" property without any error.
Does anyone has any solution?
Your code is technically correct, but you are sending an illigal value for the managerproperty.
The manager property is a distinguished name, not just the name of the person
directoryEntry.Properties["manager"].Value = "John Doe";
Will throw
A constraint violation occurred.
Change your code to something like:
directoryEntry.Properties["manager"].Value = "uid=john.doe,ou=People,dc=example,dc=com";
We are using the foll. code to retrieve the AD users and their details:-
We get error at line: SearchResultCollection resultCol =
search.FindAll();
Exception is: DirectoryServiceCOMException: An operations error
occurred. at System.DirectoryServices.DirectoryEntry.Bind(Boolean
throwIfFail) at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject() at
System.DirectoryServices.DirectorySearcher.FindAll(Boolean
findMoreThanOne) at
SharePointProject20.VisualWebPart1.VisualWebPart1.GetADUsers()
public List<Users> GetADUsers()
{
try
{
List<Users> lstADUsers = new List<Users>();
string DomainPath = "LDAP://DC=SYSDOM,DC=local";
DirectoryEntry searchRoot = new DirectoryEntry(DomainPath);
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.Filter = "(&(objectClass=user)(objectCategory=person))";
search.PropertiesToLoad.Add("samaccountname");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("usergroup");
search.PropertiesToLoad.Add("displayname");//first name
SearchResult result;
SearchResultCollection resultCol = search.FindAll();
if (resultCol != null)
{
for (int counter = 0; counter < resultCol.Count; counter++)
{
string UserNameEmailString = string.Empty;
result = resultCol[counter];
if (result.Properties.Contains("samaccountname") &&
result.Properties.Contains("mail") &&
result.Properties.Contains("displayname"))
{
Users objSurveyUsers = new Users();
objSurveyUsers.Email = (String)result.Properties["mail"][0] +
"^" + (String)result.Properties["displayname"][0];
objSurveyUsers.UserName = (String)result.Properties["samaccountname"][0];
objSurveyUsers.DisplayName = (String)result.Properties["displayname"][0];
lstADUsers.Add(objSurveyUsers);
}
}
}
return lstADUsers;
}
catch (Exception ex)
{
return null;
}
}
public class Users
{
public string Email { get; set; }
public string UserName { get; set; }
public string DisplayName { get; set; }
public bool isMapped { get; set; }
}
What could be the issue?
Our domain name is SYSDOM.local
Could it be related to permissions (how do I verify this with network admin guys?), or do I need to explicitly pass username/password?
Code reference: http://www.codeproject.com/Tips/599697/Get-list-of-Active-Directory-users-in-Csharp
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. You can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
using (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.....
}
}
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.
Constructing the PrincipalContext like shown in the sample will automatically connect to the current AD domain with the current user credentials. If you need to, you can specify other containers or domains to bind to, or you can also supply alternative credentials by using other overloads of the PrincipalContext constructor
The issue was resolved after using the HostingEnvironment.Impersonate() in PageLoad:-
Example:
using (HostingEnvironment.Impersonate()) {
GetADUsers();
}
I have created the following method in a custom Active Directory RoleProvider:
public override string[] GetRolesForUser(string username)
{
ArrayList results = new ArrayList();
using (var principalContext = new PrincipalContext(
ContextType.Domain, null, domainContainer))
{
var user = UserPrincipal.FindByIdentity(
principalContext, IdentityType.SamAccountName, username);
foreach (string acceptibleGroup in GroupsToInclude)
{
GroupPrincipal adGroup = GroupPrincipal.FindByIdentity(
principalContext, acceptibleGroup);
if (user.IsMemberOf(adGroup))
results.Add(acceptibleGroup);
}
}
return results.ToArray(typeof(string)) as string[];
}
It only checks against a white list of roles which are used in my application. The problem is that if the user is not a member of one of the roles, I get a PrincipalOperationException when the
if (user.IsMemberOf(adGroup))
line is executed. I would expect this to simply return `false if the user is not in the group. What is going wrong here?
EDIT:
As and aside, if I call user.GetAuthorizationGroups() and attempt to loop through the results, I get a COMException - The specified directory service attribute or value does not exist.
Both Principal.IsMemberOf() and user.GetAuthorizationGroups() are using tokenGroups attribute to determine the group membership.
You need to make sure the account you used to run the program is added to Builtin\Windows Authorization Access Group in order to access tokenGroups attribute.
See this MSDN KB for more details.
I have managed to work around this problem with the following:
public override string[] GetRolesForUser(string username)
{
ArrayList results = new ArrayList();
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, null, domainContainer))
{
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, username);
foreach (string acceptibleGroup in GroupsToInclude)
{
GroupPrincipal p = GroupPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, acceptibleGroup);
if (p.GetMembers().Contains(user))
results.Add(acceptibleGroup);
}
}
return results.ToArray(typeof(string)) as string[];
}
However it is not exactly efficient as it pulls all the members of a group back. I am sure there is a better solution to my problem and hopefully someone will post it here!
This is not so much a question as information for anyone experiencing the same problem.
The following error occurs:
System.DirectoryServices.AccountManagement.PrincipalOperationException: An error (87) occurred while enumerating the groups. The group's SID could not be resolved.
at System.DirectoryServices.AccountManagement.SidList.TranslateSids(String target, IntPtr[] pSids)
at System.DirectoryServices.AccountManagement.SidList.ctor(List`1 sidListByteFormat, String target, NetCred credentials)
at System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.TranslateForeignMembers()
When the following code is run and a group or child group contains a ForeignSecurityPrincipal:
private static void GetUsersFromGroup()
{
var groupDistinguishedName = "CN=IIS_IUSRS,CN=Builtin,DC=Domain,DC=com";
//NB: Exception thrown during iteration of members rather than call to GetMembers.
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "Domain", "Username", "Password"))
{
using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, groupDistinguishedName))
{
using (var searchResults = groupPrincipal.GetMembers(true))//Occurs when false also.
{
foreach (UserPrincipal item in searchResults.OfType())
{
Console.WriteLine("Found user: {0}", item.SamAccountName)
}
}
}
}
}
I raised a support call with Microsoft and they have confirmed it as an issue. A bug has been raised internally but it has not been confirmed whether this will be fixed.
Microsoft suggested the following workaround code but it performs poorly on groups with a large number of users because of the repeated calls to UserPrincipal.FindByIdentity.
class Program
{
//"CN=IIS_IUSRS,CN=Builtin,DC=dev-sp-sandbox,DC=local"; //TODO MODIFY THIS LINE ACCORDING TO YOUR DC CONFIGURATION
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Usage: ListGroupMembers \"group's DistinguishedName\"");
Console.WriteLine("Example: ListGroupMembers \"CN=IIS_IUSRS,CN=Builtin,DC=MyDomain,DC=local\"");
return;
}
string groupDistinguishedName = args[0];
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "dev-sp-dc", "Administrator", "Corp123!");
List<UserPrincipal> users = new List<UserPrincipal>();
listGroupMembers(groupDistinguishedName, ctx, users);
foreach (UserPrincipal u in users)
{
Console.WriteLine(u.DistinguishedName);
}
}
//Recursively list the group's members which are not Foreign Security Principals
private static void listGroupMembers(string groupDistinguishedName, PrincipalContext ctx, List<UserPrincipal> users)
{
DirectoryEntry group = new DirectoryEntry("LDAP://" + groupDistinguishedName);
foreach (string dn in group.Properties["member"])
{
DirectoryEntry gpMemberEntry = new DirectoryEntry("LDAP://" + dn);
System.DirectoryServices.PropertyCollection userProps = gpMemberEntry.Properties;
object[] objCls = (userProps["objectClass"].Value) as object[];
if (objCls.Contains("group"))
listGroupMembers(userProps["distinguishedName"].Value as string, ctx, users);
if (!objCls.Contains("foreignSecurityPrincipal"))
{
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, dn);
if(u!=null) // u==null for any other types except users
users.Add(u);
}
}
}
}
The above code could be modified to find foreign security principals causing problems in groups.
Microsoft provided the following information about the foreign security principals:
This is a class of objects in AD which represents a security principal from an external source (so another forest/domain or one of the “special” accounts below).
The class is documented here: http://msdn.microsoft.com/en-us/library/cc221858(v=PROT.10).aspx
And the container is documented here : http://msdn.microsoft.com/en-us/library/cc200915(v=PROT.10).aspx
A FSP is not a real object in AD, but rather a placeholder (pointer) to an object which lives in a different, trusted domain/forest. It can also be one of the “special identities” which are a bunch of well-known accounts who are also classed as FSP’s because their SID’s are different to the domain SID.
For example the anonymous, Authenticated User, batch and several other accounts as documented here:
http://technet.microsoft.com/en-us/library/cc779144(v=WS.10).aspx
Sure this is an old thread, but might help someone. I used the below code block the solve the problem. the Principal class exposes a property called StructuralObjectClass which tells you what is the AD Class of that principal. I used this to decide whether the object is a user. The GetMembers(true) recursively searches all nested-members in the groupPrincipal in question.
Hope this helps someone.
List<UserPrincipal> members = new List<UserPrincipal>();
foreach (var principal in groupPrincipal.GetMembers(true))
{
var type = principal.StructuralObjectClass;
if (type.Contains("user"))
members.Add((UserPrincipal)principal);
}
Thanks,
R
The accountmanagement library has many saddening defects, this is just another of the many...
One thing you can do to make things slightly faster would be to adjust your LDAP query so that it checks both group membership and object type at the same time as part of the query instead of in the loop. Honestly I doubt it will make much difference though.
Most of the inspiration for the query came from How to write LDAP query to test if user is member of a group?.
Query: (&(!objectClass=foreignSecurityPrincipal)(memberof=CN=YourGroup,OU=Users,DC=YourDomain,DC=com))
Note: This is an untested query...
IF there was a way to run an LDAP query in AccountManagement (another gripe of mine) then this would be the end of your troubles as you could run the query and let AccountManagement take it from there, but this option does not exist...
Based on personal experience I don't see any other options if you stick with AccountManagement. What you could do is dump AccountManagement and use just DirectoryServices. Under the hood all AccountManagement does is wrap DirectoryEntry objects anyways, you could write a few helper classes to do similar things.
As an alternative, you can use this code to get the members:
var pth = "LDAP://ex.invalid/CN=grpName,OU=Groups,OU=whatever,DC=ex,DC=invalid";
var dirEntry = new DirectoryEntry(pth);
var members = dirEntry.Invoke("Members"); //COM object
foreach (var member in (IEnumerable)members) {
var userEntry = new DirectoryEntry(member); //member is COM object
var sid = new SecurityIdentifier((byte[]) userEntry.InvokeGet("objectSid"), 0);
var typ = typeof(System.Security.Principal.NTAccount);
var account = (NTAccount)sid.Translate(typ);
Console.WriteLine(account.Value);
}
I'm creating an LDAP class that contains a function that returns the managers username of the current user.
I know that I can use the "manager" attribute to return CN="name", OU="group", DC="company" etc.
I specifically want the managers username, does anyone know if there is an attribute string that I can send to LDAP that specifically gets the managers username only? If not, is there an alternative method to do so?
The above GetManager works great except when no manager has been set up. Slight alteration to accommodate this scenario:
// return manager for a given user
public UserPrincipal GetManager(PrincipalContext ctx, UserPrincipal user) {
if (user != null) {
// get the DirectoryEntry behind the UserPrincipal object
var dirEntryForUser = user.GetUnderlyingObject() as DirectoryEntry;
if (dirEntryForUser != null) {
// check to see if we have a manager name - if so, grab it
if (dirEntryForUser.Properties["manager"] != null && dirEntryForUser.Properties["manager"].Count > 0) {
string managerDN = dirEntryForUser.Properties["manager"][0].ToString();
// find the manager UserPrincipal via the managerDN
return UserPrincipal.FindByIdentity(ctx, managerDN);
}
}
}
return null;
}
I have figured out the resolution to this now.
Basically, the manager attribute in LDAP retrives the distinguishedName attribute of the maanger user.
So if I search LDAP for the user containing the distinguishedName that is returned from the manager then I can get any of their attributes that way.
One possible solution would be to use something like this - this requires that you're on .NET 3.5 and you have referenced both System.DirectoryServices as well as System.DirectoryServices.AccountManagement:
// return manager for a given user
public UserPrincipal GetManager(PrincipalContext ctx, UserPrincipal user)
{
UserPrincipal result = null;
if (user != null)
{
// get the DirectoryEntry behind the UserPrincipal object
DirectoryEntry dirEntryForUser = user.GetUnderlyingObject() as DirectoryEntry;
if (dirEntryForUser != null)
{
// check to see if we have a manager name - if so, grab it
if (dirEntryForUser.Properties["manager"] != null)
{
string managerDN = dirEntryForUser.Properties["manager"][0].ToString();
// find the manager UserPrincipal via the managerDN
result = UserPrincipal.FindByIdentity(ctx, managerDN);
}
}
}
return result;
}
And you could then call this method e.g. like this:
// Create default domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find yourself - you could also search for other users here
UserPrincipal myself = UserPrincipal.Current;
// get the manager for myself
UserPrincipal myManager = GetManager(ctx, myself);
Both examples work fine. But I believe there's space for improvements.
Methods should not be checking if parameter is null, and returning null is a bad habit. hrowing exceptions, on the other hand, is more adequate.
Instead of asking for a context it should be possible to retrieve the context from the provided UserPrincipal, thus there shouldn't be any problems finding the current user.
public static UserPrincipal GetManager(UserPrincipal user)
{
var userEntry = user.GetUnderlyingObject() as DirectoryEntry;
if (userEntry.Properties["manager"] != null
&& userEntry.Properties["manager"].Count > 0)
{
string managerDN = userEntry.Properties["manager"][0].ToString();
return UserPrincipal.FindByIdentity(user.Context,managerDN);
}
else
throw new UserHasNoManagerException();
}
class UserHasNoManagerException : Exception
{ }