C# Active Directory Library - c#

Has anyone seen any solid libraries for working with active directory (mainly user related stuff) in C# and asp.net. Am I better off intergrating with asp membership or building something customised.
I took a look at LINQtoAD but it doesnt seem to be active anymore.

Is the System.DirectoryServices assembly and namespace insufficient?

If you're on .NET 3.5, also check out System.DirectoryServices.AccountManagement for much simpler interface when it comes to handling principals - users, groups, computers etc.
Check out this MSDN article as a great intro into S.DS.AD:
Managing Directory Security Principals in the .NET Framework 3.5
Cheers!

You can refer my OSS project which base on ActiveRecord pattern as following(Because it is open source you can find out how to operate the AD with DirectoryEntry, DirectoryEntry is not only support the LDAP protocol but also IIS, WIN and so on, so I develop this lib):
Eg: Update a user AD object.
using (var userObject = UserObject.FindOneByCN(this.ADOperator, “pangxiaoliang”))
{
if(userObject.Email == "example#landpy.com")
{
userObject.Email = "mv#live.cn";
userObject.Save();
}
}
Eg: Query user AD objects.
// 1. CN end with "liu", Mail conatains "live" (Eg: mv#live.cn), job title is "Dev" and AD object type is user.
// 2. CN start with "pang", Mail conatains "live" (Eg: mv#live.cn), job title is "Dev" and AD object type is user.
IFilter filter =
new And(
new IsUser(),
new Contains(PersonAttributeNames.Mail, "live"),
new Is(PersonAttributeNames.Title, "Dev"),
new Or(
new StartWith(AttributeNames.CN, "pang"),
new EndWith(AttributeNames.CN, "liu")
)
);
// Output the user object display name.
foreach (var userObject in UserObject.FindAll(this.ADOperator, filter))
{
using (userObject)
{
Console.WriteLine(userObject.DisplayName);
}
}
Eg: Custom query.
IFilter filter =
new And(
new IsUser(),
new Custom("(!userAccountControl:1.2.840.113556.1.4.803:=2)")
);
// Output the user object display name.
foreach (var userObject in UserObject.FindAll(this.ADOperator, filter))
{
using (userObject)
{
Console.WriteLine(userObject.DisplayName);
}
}
https://landpyactivedirectory.codeplex.com/documentation
And you will find it easy to operate the AD with it, if you have no interest with it please ignore my answer. Any question about AD please contact me :)

Related

AD UserPrincipal ChangePassword method creates user profile folders in C:\Users

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.

Why is the ObjectSecurity property in the DirectoryEntry instance of a GroupPrincipal always null?

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.

How to change DCOM config identity programmatically

Is there any way to get the information about Launching identity of DCOM application programmatically. See the picture attached to understand what i mean.
I tried to use WMI
ManagementObjectSearcher s = new ManagementObjectSearcher(new ManagementScope(#"\\.\root\cimv2"), new ObjectQuery(
"select * from Win32_DCOMApplicationSetting where AppID='{048EB43E-2059-422F-95E0-557DA96038AF}'"))
ManagementObjectCollection dcomSett = s.Get();
var value = dcomSett.Cast<ManagementObject>().ToArray()
[0].Properties["RunAsUser"].Value;
but "RunAsUser" property was empty.
Also tried Interop.COMAdmin
COMAdmin.COMAdminCatalogClass catalog = (COMAdmin.COMAdminCatalogClass)new COMAdmin.COMAdminCatalog();
(COMAdmin.COMAdminCatalogCollection)catalog.GetCollection("Applications")
in this way i managed to get applications which are listed under the "COM+ Applications" node in the "Component Services" snap-in of MMC:
I'm new in COM, DCOM, COM+ stuff and sure that i missed something important.
After a while i found out why i used to get NULL in the first approach (ManagementObject).
You will receive:
NULL if identity is currently set to The launching user
"Interactive User" in case of "The interactive user"
some string with username in case of third option (see the first picture)
But still i need a way to change identity for items like Microsoft PowerPoint Slide under DCOM Config node in MMC.
In the DCOM config, if you are using a specific user for the identity and you want to update the password via code, you need to update it in the Local Security Authority (LSA). This is possible with Windows API calls. MS has some sample code for a utility called dcomperm that does it, and you can see how they implemented in C++. You could make the same calls in C#. See the SetRunAsPassword method here. They are using the method LsaOpenPolicy to get a handle to the policy and calling LsaStorePrivateData to update the password. Then they are adding "login as a batch job" access to the account (but that shouldn't be necessary if you are only changing the password).
This sample code on pinvoke.net looks like it is making the requisite calls, except for the optional part about granting the login as a batch job permission. Note the "key" in the LSA is in the format SCM:{GUID-of-DCOM-object} Example: SCM:{00000000-0000-0000-0000-000000000000}
Oh, and I should mention as an aside that if you wanted to change the RunAs user itself (i.e. the username), you'd need to also update that in the windows registry directly (AFAIK that's the only way to do it). DCOM entries are stored under HKLM\SOFTWARE\Classes\AppID. You can do that with WMI or just use the Registry classes in .NET.
This is very simple , you can get APPId from
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppID\{048EB43E-2059-422F-95E0-557DA96038AF}
using
(RegistryKey dcomPPTIdentity = Registry.LocalMachine.OpenSubKey("Software\\Classes\\AppID\\{048EB43E-2059-422F-95E0-557DA96038AF}"))
{
if (dcomPPTIdentity != null)
{
Registry.SetValue(dcomPPTIdentity.ToString(), "RunAs", "userName");
}
}
I am using COMAdmin DLL successfully. Try something like this:
COMAdminCatalog catalog = new COMAdminCatalog();
COMAdminCatalogCollection applications = catalog.GetCollection("Applications");
applications.Populate();
for (int i = 0; i < applications.Count; i++)
{
COMAdminCatalogObject application = COMAppCollectionInUse.Item[i];
if (application.Name == "Your COM+ application name")
{
application.Value["Identity"] = "nt authority\\localservice"; // for example
}
}
This works for me on my development server. Keep in mind, it is run against the server directly on the server
using COMAdmin;
using System;
namespace ComComponents
{
class Program
{
static void Main(string[] args)
{
COMAdminCatalog catalog = new COMAdminCatalog();
COMAdminCatalogCollection applications = catalog.GetCollection("Applications");
applications.Populate();
for (int i = 0; i < applications.Count; i++)
{
COMAdminCatalogObject application = applications.Item[i];
Console.WriteLine(application.Name);
Console.WriteLine(application.Value["Identity"]);
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
}

Use Exchange Recipient Update Service via c# without PowerShell

I'm searching to get the current members of a dynamic distribution group by exchange servers. Dynamic distribution groups are based on a specified filter. The "Recipient Update Service" (RUS) find each contact by runtime, based on this filter.
I've found a lot of information to solve the problem by using a wrapper class of exchange powershell in interaction of classic commandline arguments. But this is not my intended way.
I thought there should be a special command of "Exchange Web Services" (EWS) to get the dynamic members by runtime or by interop. I was unable to find some information about this.
Does anybody have an idea or some information to solve this problem via c#?
DirectoryServices seems to do the trick for me. Create a DirectoryEntry pointing to the dynamic distribution list (schema class name = "msExchDynamicDistributionList"), and then use the "msExchDynamicDLBaseDN" and "msExchDynamicDLFilter" properties to search for the members:
using (var group = new DirectoryEntry("LDAP://CN=MyGroup,OU=MyOU,DC=company,DC=com"))
{
string baseDN = (string)group.Properties["msExchDynamicDLBaseDN"].Value;
string filter = (string)group.Properties["msExchDynamicDLFilter"].Value;
using (var searchRoot = new DirectoryEntry("LDAP://" + baseDN))
using (var searcher = new DirectorySearcher(searchRoot, filter, propertiesToLoad))
using (var results = searcher.FindAll())
{
foreach (SearchResult result in results)
{
// Use the result
}
}
}
Remember that the members of the group could be regular groups or other dynamic distribution groups as well as users, contacts and public folders.

getting access to outlook exchange global address book

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);
}

Categories

Resources