Connect to active directory with PrincipalContext - c#

Im writtem app that looks for a user in active directory based on there name
When I try to create new PrincipalContext with ContextType.Domain and the domain name as a string, I get the exception "The server could not be contacted."
So to get the the AD that I did this ...
Open System by clicking the Start button Picture of the Start button, clicking Control Panel, clicking System and Maintenance, and then clicking System.
Which I got from http://windows.microsoft.com/en-gb/windows-vista/find-the-domain-your-computer-is-on
Which gave me
Something.Something (not this, but two strings with a . between them)
So when I run the following code and enter Something.Something as the domain, I get the exception "The server could not be contacted." on the new PrincipalContext(ContextType.Domain, domain);
I have tried changing the case of the string and the but dont seem to have any luck.
So what should I be using for the domain?
public static void Main(string[] args)
{
try
{
Console.WriteLine("Enter Domain, then press enter.");
string domain = Console.ReadLine();
Console.WriteLine("Enter First Name, then press enter.");
string userName = Console.ReadLine();
//This is the line that always crashes throws error
var principalContext = new PrincipalContext(ContextType.Domain, domain);
var user = UserPrincipal.FindByIdentity(principalContext, userName);
if (user == null)
{
Console.WriteLine("User Not Found");
}
var groups = user.GetGroups(principalContext);
var result = new List<string>();
groups.ToList().ForEach(sr => result.Add(sr.SamAccountName));
foreach (var item in result)
{
Console.WriteLine(item);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}

Ashley is correct... assuming you are running the application on a machine that is joined to the domain.
using System;
using System.DirectoryServices.AccountManagement;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Enter First Name, then press enter.");
var userName = Console.ReadLine();
// Will search the domain the application is running on
var principalContext = new PrincipalContext(ContextType.Domain);
var user = UserPrincipal.FindByIdentity(principalContext, userName);
if (user == null)
{
Console.WriteLine("User Not Found");
}
else
{
// Gets a list of the user's groups
var groups = user.GetGroups().ToList();
// Loops the groups and prints the SamAccountName
groups.ForEach(g => Console.WriteLine(g.SamAccountName));
}
Console.ReadKey();
}
}
}

If you've got several seconds to spare waiting for your data form a large AD, then go ahead and use PrincipalContext but if you want your response in milliseconds, use DirectoryEntry, DirectorySearcher and .PropertiesToLoad.
Here's an example for getting the groups for a user:
https://stackoverflow.com/a/65986796/5248400

Related

C# Error when adding existing user to existing group

I have two sites, each has its own Scheduler with enough rights to do their job. We created an application to allow them to quickly add a user to AD with every thing the user will need. The problem is that sometimes, a student will transfer from one site to the other temporarily. While at the new site, they will need access to all their original stuff, plus access to the new sites stuff as well.
I had a request to add a button that will quickly allow them to add the other sites groups and not remove the original sites groups.
The error I am getting is:
Error adding User to Group.
System.Directory.Services.AccountManagement.NoMatchingPrincipalException:
No principal matching the specified paramenters was found
at
System.DirectoryServices.AccountManagement.principalCollection.add(principalContext context, identitytype itedentity type, string identitiyvalue)
at AddStudentUser.Form2.AddUserToGroup_fm2(string userID, string groupName, string siteName) identitiyvalue
c:\projects\AddStudentUser\AddStudentUser\Form2.cs:line 113
Here is the code I am calling:
private void btnUpdateExit_Click(object sender, EventArgs e)
{
//userUPN will equal Loginname#domain.com
string userUPN = this.tbUserLoginName.Text.Trim().ToString() + Form1.Globs.strUPN;
if (this.cbSite1.Checked == true & this.cbSite1.Enabled==true)
{
AddUserToGroup_fm2(userUPN, "Site1", Form1.Globs.strSUUADC);
AddUserToGroup_fm2(userUPN, "Crew_Site1", Form1.Globs.strSite1ADC);
AddUserToGroup_fm2(userUPN, "WWW-Site1", Form1.Globs.strSite1ADC);
MessageBox.Show("User has been added to the Site1 Groups.");
}
if (this.cbSite2.Checked == true & this.cbSite2.Enabled == true)
{
AddUserToGroup_fm2(userUPN, "Site2", Form1.Globs.strWRIADC);
AddUserToGroup_fm2(userUPN, "Crew_Site2", Form1.Globs.strSite2ADC);
AddUserToGroup_fm2(userUPN, "WWW-Site2", Form1.Globs.strSite2ADC);
MessageBox.Show("User has been added to the Site2 Groups.");
}
this.Close();
}
public void AddUserToGroup_fm2(string userId, string groupName, string siteName)
{
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, siteName))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, groupName);
group.Members.Add(pc, IdentityType.UserPrincipalName, userId);
group.Save();
}
}
catch (Exception E)
{
MessageBox.Show("Error adding User to Group. " + E);
}
}
The weird thing is that if I add a new user, the same code on a different form works without issue. I initially tried calling that code but was getting the same error as I am getting now. I wanted to separate the code so that I could make changes to it without affecting the original function, since it works fine when adding a new user to existing groups.
Any assistance is appreciated.
IdentityType.SamAccountName
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, siteName))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, groupName);
group.Members.Add(pc, IdentityType.SamAccountName, userId);
group.Save();
}

Error adding/removing Outlook Distribution List users through .NET

I am a co-owner of several Outlook Distribution Lists (DL's). I can edit them in Outlook, adding and removing members directly in there. However, I cannot edit them through a simple .NET program:
using System;
using System.DirectoryServices.AccountManagement;
namespace DL_Remove_User
{
class Program
{
static void Main(string[] args)
{
try
{
RemoveUser("My Distribution List", "jimtut");
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.ToString());
}
}
private static void RemoveUser(string dl, string username)
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "CORP"))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, dl);
bool result = group.Members.Remove(pc, IdentityType.SamAccountName, username);
Console.WriteLine(result.ToString());
group.Save();
}
}
}
}
This same code works on many other DL's, but for a couple, I get the message "Access is Denied". Full stack trace:
at System.DirectoryServices.Interop.UnsafeNativeMethods.IAds.SetInfo()
at System.DirectoryServices.DirectoryEntry.CommitChanges()
at System.DirectoryServices.AccountManagement.ADStoreCtx.UpdateGroupMembership(Principal group, DirectoryEntry de, NetCred credentials, AuthenticationTypes authTypes)
at System.DirectoryServices.AccountManagement.SDSUtils.ApplyChangesToDirectory(Principal p, StoreCtx storeCtx, GroupMembershipUpdater updateGroupMembership, NetCred credentials, AuthenticationTypes authTypes)
at System.DirectoryServices.AccountManagement.ADStoreCtx.Update(Principal p)
at System.DirectoryServices.AccountManagement.Principal.Save()
at Department_Distribution_Lists.Program.RemoveUser(String dl, String username) in Program.cs:line 483
Of course, "Access is denied" does indicate a permission problem, but I can edit these DL's directly in Outlook. I can even query the DL "owners" in AD/LDAP, and I'm in the collection "msExchCoManagedByLink".
Any thoughts on why I can edit in Outlook but not through .NET?
I finally figured this out. I was confused by this permissions problem since I could edit the DL in Outlook, but not thru .NET.
I started looking for differences between the DL's that I could edit thru .NET and those that I could not, and found the difference was represented in the AD property shown in this GUI as "Manager can update membership list":
Even though I was the "manager" (list owner), if the DL didn't have that property set, I could ONLY edit in Outlook.
I didn't want to have to visually check all the DL's, so I wrote the following code to detect the "real" owners/editors of a DL:
static List<string> GetGroupOwners(GroupPrincipal group)
{
List<string> owners = new List<string>();
DirectoryEntry deGroup = group.GetUnderlyingObject() as DirectoryEntry;
ActiveDirectorySecurity ads = deGroup.ObjectSecurity;
AuthorizationRuleCollection rules = ads.GetAccessRules(true, true, typeof(SecurityIdentifier));
Guid exRight_Member = new Guid("{bf9679c0-0de6-11d0-a285-00aa003049e2}");
foreach (ActiveDirectoryAccessRule ar in rules)
{
if (ar.ActiveDirectoryRights.HasFlag(ActiveDirectoryRights.GenericWrite) || (ar.ObjectType.Equals(exRight_Member) && ar.ActiveDirectoryRights.HasFlag(ActiveDirectoryRights.WriteProperty)))
{
string friendlyName = "";
try
{
friendlyName = ar.IdentityReference.Translate(typeof(NTAccount)).Value;
}
catch
{
}
owners.Add(friendlyName);
}
}
return owners;
}
If you want to know who has Outlook-based edit access, that's different:
static List<string> GetGroupOwnersOutlook(GroupPrincipal group)
{
List<string> owners = new List<string>();
DirectoryEntry deGroup = group.GetUnderlyingObject() as DirectoryEntry;
System.DirectoryServices.PropertyCollection r = deGroup.Properties;
foreach (string a in r["managedBy"])
{
owners.Add(a);
}
foreach (string a in r["msExchCoManagedByLink"])
{
owners.Add(a);
}
return owners;
}

Get Active Directory user information

Is there a way to retrieve the users active directory information like ad groups, SID and so on?
Currently I could find only examples with active directory azure.
At the moment I catch user information the following way:
IReadOnlyList<User> users = await User.FindAllAsync(UserType.LocalUser);
foreach (User user in users)
{
string displayName = (string)await user.GetPropertyAsync(KnownUserProperties.DisplayName);
}
Also the Win10 SDK doesn't provide any examples regarding active directory.
[Edit] There is a WinRT Api request on https://wpdev.uservoice.com/forums/110705-universal-windows-platform/suggestions/12556779-get-active-directory-user-information
[Edit] Regarding Issue on corefx https://github.com/dotnet/corefx/issues/7325
From command line you can use whoami /groups.
From C# you can check membership in a group using the SID using something like this:
using System;
using System.Security.Principal;
namespace ADMembership
{
class Program
{
static void Main()
{
WindowsPrincipal myPrincipal = new
WindowsPrincipal(WindowsIdentity.GetCurrent());
String groupName = "A Group";
SecurityIdentifier sid = new SecurityIdentifier(
"S-1-1-22-999999999-999999999-999999999-4444");
if (myPrincipal.IsInRole(groupName))
{
Console.WriteLine("{0} is in {1}.",
myPrincipal.Identity.Name.ToString(), groupName);
}
else
{
Console.WriteLine("{0} is not in {1}.",
myPrincipal.Identity.Name.ToString(), groupName);
}
if (myPrincipal.IsInRole(sid))
{
Console.WriteLine("{0} has SID: {1}.",
myPrincipal.Identity.Name.ToString(), sid);
}
else
{
Console.WriteLine("{0} does not have SID {1}.",
myPrincipal.Identity.Name.ToString(), sid);
}
}
}
}

Adding Local User to Local Admin Group

I am writing a C# program to be pushed out the labs I work in. The program is to create a local admin account(itadmin), set the password, set the password to never expire, and add the account to the local Administrators group. The program creates the new user account and sets everything correctly but when it attempts to add it to the admin group I get a very nondescript exception. Do I have the add to group correct in the first place? What am I missing?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
namespace CreateITAdmin
{
class Program
{
static void Main(string[] args)
{
try
{
string userName = "itadmin";
string userPassword = "password";
Console.WriteLine("Building System Information");
DirectoryEntry localMachine = new DirectoryEntry("WinNT://.,computer");
DirectoryEntry newUser = localMachine.Children.Add(userName, "user");
DirectoryEntry admGroup = new DirectoryEntry("WinNT://./Administrators,group");
Console.WriteLine("Building User Information");
newUser.Properties["FullName"].Value = "IT Administrative User";
newUser.Invoke("Put", new object[] { "UserFlags", 0x10000 });
Console.WriteLine("Setting User Password");
newUser.Invoke("SetPassword", new object[] { userPassword });
newUser.CommitChanges();
Console.WriteLine("Adding itadmin to Administrators Group");
admGroup.Invoke("Add", "WinNT://./" + newUser);
Console.WriteLine("Cleaning Up");
localMachine.Close();
newUser.Close();
admGroup.Close();
}
catch (System.DirectoryServices.DirectoryServicesCOMException E)
{
Console.WriteLine(E.Message.ToString());
Console.ReadLine();
}
catch (System.Runtime.InteropServices.COMException E)
{
Console.WriteLine(E.Message.ToString());
Console.ReadLine();
}
catch (System.Reflection.TargetInvocationException E)
{
Console.WriteLine(E.Message.ToString());
Console.ReadLine();
}
catch (Exception E)
{
Console.WriteLine(E.Message.ToString());
Console.ReadLine();
}
Console.WriteLine();
Console.WriteLine("Press Any Key to Continue");
Console.ReadLine();
return;
}
}
}
The code output is below:
Building System Information
Building User Information
Setting User Password
Adding itadmin to Administrators Group
Exception has been thrown by the target of an invocation.
Any insight would be greatly appriciated.
UPDATE 1:
With the help of #Grumbler85 the exceptionis listed below:
System.Reflection.TargetInvocationException: Exception has been thrown by the target
of an invocation. ---> System.Runtime.InteropServices.COMException: A member could not
be added to or removed from the local group because the member does not exist. --- End
of inner exception stacktrace --- at System.DirectoryServices.DirectoryEntry.Invoke
(String methodName,Object[]args) at CreateITAdmin.Program.Main(String[]args)in
H:\code\CS\CreateITAdmin\CreateITAdmin\Program.cs:line 37
Also with the help of #Grumbler85 I have been working on updating the library use to System.DirectoryServices.AccountManagement. It seems to be a lot easier and a lot more straight forward in use. More updates/details to come as I progress.
Update 2:
I know this is a quick follow up but I was able to complete the update to the new namespace. After a minor hiccup with defining the machine, I was able to successfully create a user, set the password, update the password to never expire, and add the user to the administrators group. Thanks to #Grumbler85 for the update to the new namespace. The new code is below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
namespace CreateITAdmin
{
class Program
{
static void Main(string[] args)
{
string userName = "itadmin";
string userPassword = "IT-Engineering1";
PrincipalContext systemContext = null;
try
{
Console.WriteLine("Building System Information");
systemContext = new PrincipalContext(ContextType.Machine, null);
}
catch (Exception E)
{
Console.WriteLine("Failed to create System Context.");
Console.WriteLine("Exception: " + E);
Console.WriteLine();
Console.WriteLine("Press Any Key to Continue");
Console.ReadLine();
return;
}
//Check if user object already exists
Console.WriteLine("Checking if User Exists.");
UserPrincipal usr = UserPrincipal.FindByIdentity(systemContext, userName);
if (usr != null)
{
Console.WriteLine(userName + " already exists. Exiting!!");
Console.ReadLine();
return;
}
//Create the new UserPrincipal object
Console.WriteLine("Building User Information");
UserPrincipal userPrincipal = new UserPrincipal(systemContext);
userPrincipal.Name = userName;
userPrincipal.DisplayName = "IT Administrative User";
userPrincipal.PasswordNeverExpires = true;
userPrincipal.SetPassword(userPassword);
userPrincipal.Enabled = true;
try
{
Console.WriteLine("Creating New User");
userPrincipal.Save();
}
catch (Exception E)
{
Console.WriteLine("Failed to create user.");
Console.WriteLine("Exception: " + E);
Console.WriteLine();
Console.WriteLine("Press Any Key to Continue");
Console.ReadLine();
return;
}
GroupPrincipal groupPrincipal = null;
try
{
groupPrincipal = GroupPrincipal.FindByIdentity(systemContext, "Administrators");
if (groupPrincipal != null)
{
//check if user is a member
Console.WriteLine("Checking if itadmin is part of Administrators Group");
if (groupPrincipal.Members.Contains(systemContext, IdentityType.SamAccountName, userName))
{
Console.WriteLine("Administrators already contains " + userName);
return;
}
//Adding the user to the group
Console.WriteLine("Adding itadmin to Administrators Group");
groupPrincipal.Members.Add(userPrincipal);
groupPrincipal.Save();
return;
}
else
{
Console.WriteLine("Could not find the group Administrators");
}
}
catch (Exception E)
{
Console.WriteLine("Exception adding user to group.");
Console.WriteLine("Exception: " + E);
Console.WriteLine();
Console.WriteLine("Press Any Key to Continue");
Console.ReadLine();
}
Console.WriteLine("Cleaning Up");
groupPrincipal.Dispose();
userPrincipal.Dispose();
systemContext.Dispose();
Console.WriteLine();
Console.WriteLine("Press Any Key to Continue");
Console.ReadLine();
return;
}
}
}
For Update 3 (for Multi Language support)
Please use build in identifiers --> "Well Known SIDs" for build in accounts or groups:
var sAdministrators = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid , null).Translate(typeof(NTAccount)).Value;
groupPrincipal = GroupPrincipal.FindByIdentity(systemContext, IdentityType.Name, sAdministrators.ToString());
and not: ..... FindByIdentity(systemContext, "Administrators");
Because if you want to use it "world wide" and outside of the engl. world you will get an error. Example: Germany use "VORDEFINIERT\Administratoren" as Name.
You mention that these machines are on a domain, it is much simpler to just do this with group policy.
Go in to group policy management (gpmc.msc) and create a new policy. Once you have a new policy created go to Computer Configuration->Prefrences->Local Users and Groups.
From there right click and go to New->Local User. In the new screen set the action to Create (you can click the help button to see the difference between the modes) and enter your info for the user in that screen.
One you click ok the user will show up on the screen on the local users and groups page. From there right click and go to New->Local Group. On the new page set the action to Update, use the drop-down to find the group name Administrators (built-in) and select it. In the bottom section click Add... and type in by hand the same name you put in from the previous screen (itadmin in your case). At the end it should look like this
the Local Users and Groups page will look like this
It is important to notice the Order column, the update on the administrator's group must have a higher order number than the user creation command.
One you have your group policy set up apply the policy to the machines that are in the lab (be it through OU targeting or Security Filtering, or WMI Filtering). On next reboot the local itadmin user will be created on each machine.
Also a interesting note, when you choose the user when selecting who to add to the local administrators group, you can click the ... and choose a user on the domain this will allow someone to use their domain login to be a local admin on a small set of computers without giving them rights to be a admin everywhere. However they will need to be able to log in using the domain for this to work, so if you are troubleshooting a network connectivity issue your current approach may be a better thing to do.

How to check in C# if user account is active

How can I check from C# if a local user account (namely the local Administrator account) is active?
What I actually want is a C# replacement for the "Account Active" = "Yes" (or "No") output from the "net user Administrator" command.
I'm afraid this question looks like a duplicate to this one, but I don't know what to pass in for the parameter for the root DirectoryEntry object. Tried different things like "ldap://" + Environment.MachineName, "ldap://127.0.0.1", "WinNT://" + Environment.MachineName, but none of them worked. I get an exception thrown by the searcher.FindAll() call in all three cases.
class Program
{
static void Main(string[] args)
{
// Create the context for the principal object.
PrincipalContext ctx = new PrincipalContext(ContextType.Machine);
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, "Administrator");
Console.WriteLine(String.Format("Administrator is enable: {0}", u.Enabled));
}
}
You can query WMI's Win32_UserAccount
This is boilerplate what MS's wmi code creator spits out as a reference;
using System;
using System.Management;
using System.Windows.Forms;
namespace WMISample
{
public class MyWMIQuery
{
public static void Main()
{
try
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT Disabled FROM Win32_UserAccount WHERE name = 'alexk'");
foreach (ManagementObject queryObj in searcher.Get())
{
Console.WriteLine("-----------------------------------");
Console.WriteLine("Win32_UserAccount instance");
Console.WriteLine("-----------------------------------");
Console.WriteLine("Disabled: {0}", queryObj["Disabled"]);
Console.ReadKey();
}
}
catch (ManagementException e)
{
MessageBox.Show("An error occurred while querying for WMI data: " + e.Message);
}
}
}
}
(I'd link the tool but as usual the msdn links are dead)
Try this.
var server = "YOURMACHINENAME";
var username = "Guest";
var de = new DirectoryEntry {Path = "WinNT://" + server + ",computer"};
var result = de.Children
.Cast<DirectoryEntry>()
.First<DirectoryEntry>(d => d.SchemaClassName == "User" && d.Properties["Name"].Value.ToString() == username);
var flags = (int)result.Properties["UserFlags"].Value;
var disabled = (flags & 2) == 2;
This isn't quite the same but they use DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("WinNT://{0}/{1}", computerName, username)); Would that help?
Considering it's a local user, you need to call the win32 api funcion NetGetUserInfo to get what you need.
The example in pinvoke.net is almost what you need, however you need to change the level parameter to 2 to get the neccesary info

Categories