I'm pretty new to active directory and I'm currently working on a library for a project to easily manage our active directory objects like users, resources, groups and so on.
The library is in .NetStandard 2.0 and I use the Principal classes from
System.DirectoryServices.AccountManagement
As the UserPrincipal class doesn't contain all the properties that we could need, I tried to implement an UserPrincipalExtended class that, for now, just add the Initials property.
Here is my class :
[DirectoryObjectClass("user")]
[DirectoryRdnPrefix("CN")]
public class UserPrincipalExtended : UserPrincipal
{
public UserPrincipalExtended(PrincipalContext context) : base(context) { }
public UserPrincipalExtended(PrincipalContext context, string samAccountName, string password, bool enabled) : base(context, samAccountName, password, enabled) { }
[DirectoryProperty("Initials")]
public string Initials
{
get
{
if (ExtensionGet("initials").Length != 1) return null;
return (string)ExtensionGet("initials")[0];
}
set { ExtensionSet("initials", value); }
}
public static new UserPrincipalExtended FindByIdentity(PrincipalContext context, string identityValue)
{
return (UserPrincipalExtended)FindByIdentityWithType(context, typeof(UserPrincipalExtended), identityValue);
}
public static new UserPrincipalExtended FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
{
return (UserPrincipalExtended)FindByIdentityWithType(context, typeof(UserPrincipalExtended), identityType, identityValue);
}
}
When I do a search in the active directory using the UserPrincipal class, it works as expected :
using (var context = _contextProvider.GetPrincipalContext())
using (var query = new UserPrincipal(context))
using (var searcher = new PrincipalSearcher(query))
{
foreach (var principal in searcher.FindAll())
{
UserPrincipal userPrincipal = principal as UserPrincipal;
if (CheckHelper.IsFilled(userPrincipal))
{
Console.WriteLine($"{userPrincipal.StructuralObjectClass} : {userPrincipal.SamAccountName}");
}
}
}
/*Output
user : cadmin
user : Guest
user : DefaultAccount
*/
But if I try to perform the same search using my own class, the result contains also computers :
using (var context = _contextProvider.GetPrincipalContext())
using (var query = new UserPrincipalExtended(context))
using (var searcher = new PrincipalSearcher(query))
{
foreach (var principal in searcher.FindAll())
{
UserPrincipalExtended userPrincipalExtended = principal as UserPrincipalExtended;
if (CheckHelper.IsFilled(userPrincipalExtended))
{
Console.WriteLine($"userPrincipalExtended.StructuralObjectClass} : {userPrincipalExtended.SamAccountName}");
}
}
}
/*Output
user : cadmin
user : Guest
user : DefaultAccount
computer : WS001$
computer : WS002$
computer : WS003$
*/
As my UserPrincipalExtended class has the attribute :
[DirectoryObjectClass("user")]
I thought that was enough to filter on this object type in the active directory, but it seems it does not.
Any idea of what's going on here?
Cheers
microsoft Principal types filter creating code
Also faced with this problem.Having rummaged in the source code, I found such a workaround.
[DirectoryObjectClass("user)(objectCategory=user")]
In your constructor you can set the ObjectCategory attribute to User
[DirectoryObjectClass("user")]
[DirectoryRdnPrefix("CN")]
public class UserPrincipalExtended : UserPrincipal
{
public UserPrincipalExtended(PrincipalContext context) : base(context)
{
// Set ObjectCategory to user so computer objects are not returned
ExtensionSet("objectCategory", "user");
}
[DirectoryProperty("Initials")]
public string Initials
{
get
{
if (ExtensionGet("initials").Length != 1) return null;
return (string)ExtensionGet("initials")[0];
}
set { ExtensionSet("initials", value); }
}
}
Related
I am trying to extend UserPrincipal to easily read directory properties. When I debug, the user returns null, but I can find the expected information in Current information.
Extended class
public class UserPrincipalEx: UserPrincipal
{
public UserPrincipalEx(PrincipalContext ctx) : base(ctx) { }
public new static UserPrincipalEx FindByIdentity(PrincipalContext ctx, string un)
{
return UserPrincipal.FindByIdentity(ctx, un) as UserPrincipalEx;
}
//example of property to retrieve
[DirectoryProperty("MI")]
public string MI
{
get
{
if (ExtensionGet("Initials").Length < 1) return null;
return ExtensionGet("Initials").ToString();
}
set { ExtensionSet("Initials", value); }
}
}
Code that exists elsewhere to retrieve the current user's information.
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, Environment.UserDomainName);
UserPrincipalEx user = UserPrincipalEx.FindByIdentity(ctx, un);
//more code to work with the data
Relevant debug info:
I need user to not return null.
I have searched through the "similar questions" and tried to follow their examples, but I am unable to get this to work.
Update 20230105 I tried marc_s's solution below in a new project, no joy.
Update 20230106 I tried using Convert to change the class of a UserPrincipal, also no joy. Object must implement iConvertible. UserPrincipal does not.
UserPrincipal up = UserPrincipal.FindByIdentity(ctx, un);
user = (UserPrincipalEx)Convert.ChangeType(up, typeof(UserPrincipalEx));
I've done this before - years ago - and I used to use this code:
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalEx: UserPrincipal
{
public UserPrincipalEx(PrincipalContext ctx) : base(ctx)
{ }
// Implement the constructor with initialization parameters.
public UserPrincipalEx (PrincipalContext context,
string samAccountName,
string password,
bool enabled) : base(context, samAccountName, password, enabled)
{ }
// Create the "Department" property.
[DirectoryProperty("department")]
public string Department
{
get
{
if (ExtensionGet("department").Length != 1)
return string.Empty;
return (string)ExtensionGet("department")[0];
}
set { ExtensionSet("department", value); }
}
// Create the "Manager" property.
[DirectoryProperty("manager")]
public string Manager
{
get
{
if (ExtensionGet("manager").Length != 1)
return string.Empty;
return (string)ExtensionGet("manager")[0];
}
set { ExtensionSet("manager", value); }
}
}
So just basically do NOT create a static FindByIdentity method, but implement a second constructor. Also, accessing the ExtensionGet methods seems to a tad different from your version.
To search for a user, use the FindByIdentity defined on the base class - like this:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, Environment.UserDomainName))
{
UserPrincipalEx user = UserPrincipalEx.FindByIdentity(ctx, un);
// here, "user" should be just fine,
// and the "Department" and "Manager" properties should be readable
}
This is not what I had intended, but it will suffice for this project and may be helpful to someone else in the future.
Taken from How to implement and call a custom extension method.
public static class UserEx
{
public static string MI (this UserPrincipal up)
{
return ((DirectoryEntry)up.GetUnderlyingObject()).Properties["Initials"][0].ToString();
}
}
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, Environment.UserDomainName))
{
UserPrincipal up = UserPrincipal.FindByIdentity(ctx, un);
Console.WriteLine($"{up.DisplayName}'s middle initial is {up.MI()}");
//output: John Q. Public's middle initial is Q.
}
Of course, this will only GET data, but another method could be added to SET data fairly easily.
My organisation using a combination of AD and LDS. AD syncs to LDS and stores some information within the extensionAttribute fields [mainly 10, 11 and 12].
I can pull back the standard information okay from LDS, i.e. Title, Surname, Initials but can't get the exntensionAttributes.I have used the example to Extend the UserPrincipal but still am unable to see the attribute values.
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("user")]
public class UserPrincipalEx : UserPrincipal
{
public UserPrincipalEx(PrincipalContext context)
: base(context)
{ }
public UserPrincipalEx(PrincipalContext context, string samAccountName, string password, bool enabled)
:base(context, samAccountName,password,enabled)
{ }
public static new UserPrincipalEx FindByIdentity(PrincipalContext context, string identityValue)
{
return (UserPrincipalEx)FindByIdentityWithType(context, typeof(UserPrincipalEx), identityValue);
}
public static new UserPrincipalEx FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
{
return (UserPrincipalEx)FindByIdentityWithType(context, typeof(UserPrincipalEx), identityType, identityValue);
}
[DirectoryProperty("extensionAttribute10")]
public string Ext10
{
get
{
if (ExtensionGet("extensionAttribute10").Length != 1)
return null;
return (string)ExtensionGet("extensionAttribute10")[0];
}
}
}
I then have:
PrincipalContext ctx = new PrincipalContext(ContextType.ApplicationDirectory, "LDSServerHere:389", "OU HERE", "Acccount Name Here", "Password HEre");
UserPrincipalEx u = UserPrincipalEx.FindByIdentity(ctx, IdentityType.SamAccountName, login);
string prop = string.Empty;
try
{
prop = u.Ext10;
}
catch (Exception ex)
{
prop = ex.ToString();
}
return prop;
Keep getting a NULLReferenceException: Object reference not set to an instance of an object
Am i doing something stupid here?
Calling FindByIdentityWithType won't work. Looking at the documentation, it's inherited from Principal (not UserPrincipal) and it says it's "not intended to be called directly from your code". It likely just doesn't understand your derived class, so it's returning nothing because nothing it finds matches your class.
But there's another way to do it: use DirectoryEntry.
PrincipalContext ctx = new PrincipalContext(ContextType.ApplicationDirectory, "LDSServerHere:389", "OU HERE", "Acccount Name Here", "Password HEre");
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, login);
string prop = string.Empty;
try
{
var de = (DirectoryEntry) u.GetUnderlyingObject();
if (de.Properties.Contains("extensionAttribute10")) {
prop = de.Properties["extensionAttribute10"].Value;
}
}
catch (Exception ex)
{
prop = ex.ToString();
}
return prop;
Notice the null check on the property. If the attribute is empty, it won't exist in the Properties collection at all. You could probably add some extra null checks in there just to be safe.
UserPrincipal and other classes in the AccountManagement namespace just use DirectoryEntry in the background anyway. They just don't expose all of the available attributes. So sometimes you have to use DirectoryEntry directly.
Actually, I've found that it's much faster and more efficient to only use DirectoryEntry and not use the AccountManagement namespace at all, although it can be a little more complicated to use sometimes.
Is this possible to set a condition for a "query-by-example" principal to NOT LIKE rather than LIKE ?
Like Method:
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.Name= "Mario";
This will return all users with the name "Mario".
Is this possible to create a condition to get all the users who are not named "Mario" ?
Something like this :
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.Name != "Mario";
All users with an other name than "Mario".
No, however you can get all users. Then use linq to filter them.
UserPrincipal qbeUser = new UserPrincipal(ctx);
PrincipalSearcher pSearch = new PrincipalSearcher(qbeUser);
PrincipalSearchResult<Principal> pResult = pSearch.FindAll();
var notMario = (from u in pResult
where u.Name != "Mario"
select u);
Then depending on what you want to do
foreach (Principal p in notMario) {
// Do Somthing
}
This can be achieved by extending the UserPrincipal class :
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalEXT : UserPrincipal
{
// Inplement the constructor using the base class constructor.
public UserPrincipalEXT(PrincipalContext context)
: base(context)
{ }
// Implement the constructor with initialization parameters.
public UserPrincipalEXT(PrincipalContext context,
string samAccountName,
string password,
bool enabled)
: base(context, samAccountName, password, enabled)
{ }
// Create the "employeeType" property with the "!" for NOT LIKE.
[DirectoryProperty("!employeeType")]
public string NotLikeEmployeeType
{
get
{
if (ExtensionGet("!employeeType").Length != 1)
return string.Empty;
return (string)ExtensionGet("!employeeType")[0];
}
set { ExtensionSet("!employeeType", value); }
}
// Implement the overloaded search method FindByIdentity.
public static new UserPrincipalEXT FindByIdentity(PrincipalContext context, string identityValue)
{
return (UserPrincipalEXT)FindByIdentityWithType(context, typeof(UserPrincipalEXT), identityValue);
}
// Implement the overloaded search method FindByIdentity.
public static new UserPrincipalEXT FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
{
return (UserPrincipalEXT)FindByIdentityWithType(context, typeof(UserPrincipalEXT), identityType, identityValue);
}
}
The important thing to understand is here :
// Create the "employeeType" property.
[DirectoryProperty("!employeeType")]
public string NotLikeEmployeeType
{
get
{
if (ExtensionGet("!employeeType").Length != 1)
return string.Empty;
return (string)ExtensionGet("!employeeType")[0];
}
set { ExtensionSet("!employeeType", value); }
}
Given that the "DirectoryProperty" with ExtensionGet AND ExtensionSet create the condition when the property (NotLikeEmployeeType in this case) is not empty, you can add an "!" before the AD property (employeeType in this case).
This is how we can use the extension :
UserPrincipalEXT qbeUser = new UserPrincipalEXT(ctx);
qbeUser.NotLikeEmployeeType = "exclude";
Now the condition returned will be :
!employeeType = exclude
And this is exactly what we want ! Not Like ! :)
And, the great thing about extension, is you can add AD properties to the class that they are normally not exposed (like employeeType)
I am creating user by PrincipalContext on active directory. I want to add some extra attributes like Location, Initials,EmployeeId and Manager.
I can able to add Location, Initials and EmployeeId successfully by using UserPrincipalEx class. but i am not able assign value for Manager attribute.
When I am trying to assign some values on Manager attribute, it will show an error message as A constraint violation occurred."
Here My Code is:
PrincipalContext ouContext = new PrincipalContext(ContextType.Domain, AD.ServerDomain, container, AD.LDAPUser, AD.LDAPPasswoprd);
UserPrincipalEx user = new UserPrincipalEx(ouContext);
user.GivenName = txtGivenName.Text;
user.Surname = txtSurName.Text;
user.DisplayName = Convert.ToString(txtDisplayName.Text);
user.Manager = txtSupervisor.Text; // What should I assign in this field
user.SetPassword("welcome1*");
user.Enabled = true;
user.ExpirePasswordNow();
user.Save(); // Here I am getting the error
My extension class:
[DirectoryObjectClass("user")]
[DirectoryRdnPrefix("CN")]
public class UserPrincipalEx : UserPrincipal
{
public UserPrincipalEx(PrincipalContext context) : base(context) { }
public UserPrincipalEx(PrincipalContext context, string samAccountName, string password, bool enabled) : base(context, samAccountName, password, enabled) { }
public static new UserPrincipalEx FindByIdentity(PrincipalContext context,
string identityValue)
{
return (UserPrincipalEx)FindByIdentityWithType(context,
typeof(UserPrincipalEx),
identityValue);
}
public static new UserPrincipalEx FindByIdentity(PrincipalContext context,
IdentityType identityType,
string identityValue)
{
return (UserPrincipalEx)FindByIdentityWithType(context,
typeof(UserPrincipalEx),
identityType,
identityValue);
}
[DirectoryProperty("distinguishedName")]
public string DistinguishedName
{
get
{
if (ExtensionGet("distinguishedName").Length != 1)
return null;
return (string)ExtensionGet("distinguishedName")[0];
}
set
{
ExtensionSet("distinguishedName", value);
}
}
[DirectoryProperty("manager")]
public string Manager
{
get
{
if (ExtensionGet("manager").Length != 1)
return null;
return (string)ExtensionGet("manager")[0];
}
set
{
ExtensionSet("manager", value);
}
}
Please help me to resolve this problem and How to assign values on Manager field?
The manager attribute is not free text, it is a link to another Active Directory user. In this field you should specify the Distinguished Name of the manager's user account.
For Example:
PrincipalContext ouContext = new PrincipalContext(ContextType.Domain, AD.ServerDomain, container, AD.LDAPUser, AD.LDAPPasswoprd);
UserPrincipalEx user = new UserPrincipalEx(ouContext);
user.Manager = "CN=Managing Person,OU=Users,OU=Organisation,DC=domain,DC=local";
user.Save();
There are a number of answers to this using DirectoryEntry but nothing relating to the AccountManagement classes.
Is there a way I can use AccountManagement to get a list of OU?
Personally, I think "DirectoryEntry" is probably the way to go.
But this link suggests you can use PrincipalContext:
Get Groups From OU using DirectoryServices.AccountManagement
Old question but still, i just had to solve it so i'll share what i found : You can extend the Principal Object to any ActiveDirectory Object (search Principal Extensions in learn.microsoft.com)
For exemple :
[DirectoryRdnPrefix("OU")]
[DirectoryObjectClass("organizationalUnit")]
public class OuPrincipal : GroupPrincipal
{
public OuPrincipal(PrincipalContext pc) : base(pc)
{
}
OuPrincipalSearchFilter searchFilter;
public OuPrincipalSearchFilter AdvancedSearchFilter
{
get
{
if ( null == searchFilter )
searchFilter = new OuPrincipalSearchFilter(this);
return searchFilter;
}
}
public object[] GetAttribute(string attribute)
{
return (ExtensionGet(attribute));
}
[DirectoryProperty("st")]
public string State
{
get
{
if (ExtensionGet("st").Length != 1)
return null;
return (string)ExtensionGet("st")[0];
}
}
I just needed a list of Ous With something in the "State" property so i needed to extend an advancedFilterSet
public class OuPrincipalSearchFilter : AdvancedFilters
{
public OuPrincipalSearchFilter(Principal p) : base(p){}
public void testState(string value)
{
this.AdvancedFilterSet("st", value, typeof(string), MatchType.Equals);
}
}
And then :
var test = new OuPrincipal(pc);
test.AdvancedSearchFilter.testState("*");
PrincipalSearcher ps = new PrincipalSearcher(test);
var rslts = ps.FindAll();
foreach(OuPrincipal ou in rslts)
{
Console.WriteLine("OU "+ou.Name+" : "+ou.State+" ("+ou.Description+")");
}
I hope it helps someone (or myself next time i forget...).
i used this github repository for inspiration.