I was able to get the account-disabling part of my code to work, but to keep our AD tree even more clean, we have a specifically created !Disabled OU. I'd like my code to be able to both disable the computer account and move it into the !Disabled OU.
Here's what I have so far:
string computerName = Environment.MachineName;
using (PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, null, "username", "password"))
{
ComputerPrincipal computer = ComputerPrincipal.FindByIdentity(domainContext, computerName);
if (computer != null)
{
try
{
computer.Enabled = false;
label3.Visible = true;
computer.Save();
label3.Text = "Computer was disabled in Active Directory." + "\n";
try
{
string LdapDomain = "prefix.domain.suffix";
string distinguishedName = string.Empty;
string connectionPrefix = "LDAP://" + LdapDomain;
DirectoryEntry entry = new DirectoryEntry(connectionPrefix);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=computer)(|(cn=" + computerName + ")(dn=" + computerName + ")))";
SearchResult result = mySearcher.FindOne();
if (result == null)
{
label3.Text += ("Unable to locate the distinguishedName for the object " + computerName + " in the " + LdapDomain + " domain." + "\n");
}
else if (result != null)
{
DirectoryEntry directoryObject = result.GetDirectoryEntry();
distinguishedName = "LDAP://" + directoryObject.Properties["distinguishedName"].Value;
label3.Text += ("Distinguished name is " + distinguishedName + "\n");
string newLocation = "OU=!Disabled,DC=prefix,DC=domain,DC=suffix";
DirectoryEntry nLocation = new DirectoryEntry("LDAP://" + newLocation);
string newName = directoryObject.Name;
//directoryObject.MoveTo(nLocation, newName);
DirectoryEntry moveParent = new DirectoryEntry(newLocation);
directoryObject.MoveTo(moveParent); //Comes from Microsoft example, as prior may have been possible cause of errors.
label3.Text += ("Successfully moved computer to the !Disabled OU");
nLocation.Close();
directoryObject.Close();
entry.Close();
entry.Dispose();
mySearcher.Dispose();
}
else
{
label3.Text += ("Unexpected error in moving computer.");
}
button1.Visible = true;
}
catch (Exception p)
{
label3.Text += ("Failed to move computer with exception " + p);
button1.Visible = true;
}
/*
public void Move(string objectLocation, string newLocation)
{
//For brevity, removed existence checks
DirectoryEntry eLocation = new DirectoryEntry("LDAP://" + objectLocation);
DirectoryEntry nLocation = new DirectoryEntry("LDAP://" + newLocation);
string newName = eLocation.Name;
eLocation.MoveTo(nLocation, newName);
nLocation.Close();
eLocation.Close();
}
*/
}
catch (Exception x)
{
label3.Visible = true;
label3.Text = "Unable to disable computer with exception " + x;
button1.Visible = true;
}
}
else if (computer == null)
{
label3.Visible = true;
label3.Text = "Computer was not found in Active Directory.";
button1.Visible = true;
}
else
{
label3.Visible = true;
label3.Text = "Unexpected error in computer search.";
button1.Visible = true;
}
}
The display aspects are pretty sloppy, but it's a quick and dirty Windows Form that displays all the things that are going on. The problem I'm having is that even though I have the distinguished name and can get a DirectoryEntry object from the search, when I call the MoveTo() method I get an error about the object not existing or not being found. Could someone point me in the right direction here?
I've considered binding to the two different OU's and using the DirectoryEntry.Children.Add() and DirectoryEntry.Children.Remove() methods as a workaround, but that doesn't fix the problem I'm having traversing AD.
Related
I am tasked with implementing single sign-on for our customers as part of our next release, the idea is as follows:
When our customer has access to the website, they won't need to go through an extra login step, because they are already logged into their PC with their AD account and only accounts in AD will have can access the website.
I have tried to research and learn but there seems to be no specific guide.
I'm using web forms and AD in windows server 2019.
Please suggest steps or references to me. Thanks a lot.
using System.DirectoryServices;
using System.Collections.Generic;
namespace LdapAuthentication
{
public class LdapAuthentication
{
private string _path;
private string _filterAttribute;
public LdapAuthentication(string path)
{
_path = path;
}
public bool IsAuthenticated(string domain, string username, string pwd)
{
string domainAndUsername = domain + #"\" + username;
DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, pwd);
try
{
//Bind to the native AdsObject to force authentication.
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (null == result)
{
return false;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (string)result.Properties["cn"][0];
}
catch (Exception ex)
{
throw new Exception("Error authenticating user. " + ex.Message);
}
return true;
}
public string GetGroups()
{
DirectorySearcher search = new DirectorySearcher(_path);
search.Filter = "(cn=" + _filterAttribute + ")";
search.PropertiesToLoad.Add("memberOf");
StringBuilder groupNames = new StringBuilder();
try
{
SearchResult result = search.FindOne();
int propertyCount = result.Properties["memberOf"].Count;
string dn;
int equalsIndex, commaIndex;
for (int propertyCounter = 0; propertyCounter < propertyCount; propertyCounter++)
{
dn = (string)result.Properties["memberOf"][propertyCounter];
equalsIndex = dn.IndexOf("=", 1);
commaIndex = dn.IndexOf(",", 1);
if (-1 == equalsIndex)
{
return null;
}
groupNames.Append(dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex) - 1));
groupNames.Append("|");
}
}
catch (Exception ex)
{
throw new Exception("Error obtaining group names. " + ex.Message);
}
return groupNames.ToString();
}
}
}
I tried to create reset password and user creation functions for Active Directory. On my PC with below code is works just fine without any error. But when I publish to the server, I received error: Exception has been thrown by the target of an invocation.
ADResult hasil = new ADResult();
DirectoryEntry de = new DirectoryEntry(_path, _adminID, _adminPassword, AuthenticationTypes.Secure);
DirectorySearcher ds = new DirectorySearcher(de);
string query = string.Format("(&(objectCategory=person)(sAMAccountName={0}))", user.userID);
ds.Filter = query;
ds.Sort.PropertyName = "CN";
ds.SearchScope = SearchScope.Subtree;
ds.CacheResults = false;
try
{
SearchResult sr = ds.FindOne();
if (sr == null)
{
hasil.errorCode = -1;
hasil.result = "User name not found in this domain.";
}
else
{
DirectoryEntry userCredentials = sr.GetDirectoryEntry();
userCredentials.Invoke("SetPassword", new Object[] { user.password });
userCredentials.CommitChanges();
userCredentials.Close();
hasil.errorCode = 0;
hasil.result = "Password for " + user.userID + " changed successfully.";
}
}
catch (Exception e)
{
hasil.errorCode = -1;
hasil.result = e.Message + "<br/>" + e.StackTrace + "<br/>" + e.Source;
}
return hasil;
Is there something configuration/settings that I missed on the server side?
I changed my code using
UserPrincipal
instead of
DirectoryEntry
and it works perfectly.
I use this code:
PrincipalContext PrincipalContext4 = new PrincipalContext(ContextType.Domain, "full_domain_name.com", "OU=User_OU,DC=domain_name,DC=co,DC=id", _adminID, _adminPassword);
UserPrincipal UserPrincipal1 = new UserPrincipal(PrincipalContext4, user.userID, user.password, true);
//User Logon Name
UserPrincipal1.UserPrincipalName = user.userID;
UserPrincipal1.Name = user.firstName + " " + user.lastName;
UserPrincipal1.GivenName = user.firstName;
UserPrincipal1.Surname = user.lastName;
UserPrincipal1.DisplayName = user.firstName + " " + user.lastName;
UserPrincipal1.Enabled = true;
UserPrincipal1.Save();
I still don't know why I use DirectoryEntry is not working on windows server 2019
That't not the exact error message,real error must be wrapped.You can write loggers or event logging after lines which you think could be culprit.You can check event log on that server if you can find elaborated stack Trace. You can check that user have admin privilege for that server to lookup in AD.
I'm trying to set user properties for a newly created user. Properties like samaccount and userprincipalname work but other properties like address and phone number don't. I'm making use of textboxes. Here's a property example:
newUser.Properties["givenName"].Value = txt.FName
The error I get is that the field is invalid yet the other fields named above do work. Could anyone explain why this is?
I think this help you out..
public void SetAdInfo(string objectFilter, Property objectName,
string objectValue, string LdapDomain)
{
string connectionPrefix = "LDAP://" + LdapDomain;
DirectoryEntry entry = new DirectoryEntry(connectionPrefix);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(cn=" + objectFilter + ")";
mySearcher.PropertiesToLoad.Add(""+objectName+"");
SearchResult result = mySearcher.FindOne();
if (result != null)
{
DirectoryEntry entryToUpdate = result.GetDirectoryEntry();
if (!(String.IsNullOrEmpty(objectValue)))
{
if (result.Properties.Contains("" + objectName + ""))
{
entryToUpdate.Properties["" + objectName + ""].Value = objectValue;
}
else
{
entryToUpdate.Properties["" + objectName + ""].Add(objectValue);
}
entryToUpdate.CommitChanges();
}
}
entry.Close();
entry.Dispose();
mySearcher.Dispose();
}
You can check this article for more info:
https://www.codeproject.com/Articles/18102/Howto-Almost-Everything-In-Active-Directory-via-C
The full code that creates a user is this:
private void btn_AddStudent_Click(object sender, EventArgs e)
{
try
{
// Username en wachtwoord in variabelen zetten.
string userName = generator(8);
string password = generator(8);
// Juiste OU pad aangeven. Afhankelijk van geselecteerde richting.
string ouString = "OU = " + cmb_Study.Text;
string LDAPstring = "LDAP://" + ouString + ", DC=DR, DC=GUI";
DirectoryEntry dirEntry = new DirectoryEntry(LDAPstring);
// User aanmaken.
string userString = "CN = " + userName;
DirectoryEntry newUser = dirEntry.Children.Add(userString, "user");
newUser.CommitChanges();
newUser.Properties["userPrincipalName"].Add(userName + "#DR.GUI");
newUser.Properties["sAMAccountName"].Value = userName;
newUser.Invoke("SetPassword", new object[] {password});
newUser.Properties["initials"].Value = txt_Initials;
newUser.Properties["Given-Name"].Value = txt_FName;
newUser.Properties["sn"].Value = txt_LName;
newUser.Properties["mail"].Value = txt_Mail;
newUser.Properties["mobile"].Value = txt_Mobile;
newUser.Properties["telephoneNumber"].Value = txt_Telephone;
newUser.Properties["streetAddress"].Value = txt_Street + " " + txt_Number;
newUser.Properties["postalCode"].Value = txt_PostalCode;
newUser.Close();
dirEntry.Close();
newUser.Dispose();
dirEntry.Dispose();
MessageBox.Show("User has been succesfully added");
There's a catch under that piece of code that does work.
I'm passing username and password to check whether a user is valid from Active Directory.
Here is my code :
private bool ValidUser(string name, string userPwd)
{
string UserName = "XXXXXXXXXX";
string Password = "XXXXXXXXXXXXX";
DirectoryEntry objRootEntry = new DirectoryEntry("XXXXXXXX.com", UserName, Password);
DirectorySearcher objADSearcher = new DirectorySearcher(objRootEntry);
objADSearcher.Filter = ("(&(sAMAccountType=xxxxxxxxx)(samAccountName=" + name + "))");
SearchResult objResult = objADSearcher.FindOne();
DirectoryEntry objLoginEntry = (objResult != null) ? objResult.GetDirectoryEntry() : null;
if (objLoginEntry != null)
{
return true;
}
return false;
}
Now it checks the user name alone.I need to check whether the entering password (userPwd) matches with the Active directory. How to do that.
Please help me out.
//You are entering password while finding in Directory entry is enough. Don't need to check again
Check this detail code
public bool ValidateUser(string domain, string username, string password,string LdapPath, out string Errmsg)
{
Errmsg = "";
string domainAndUsername = domain + #"\" + username;
DirectoryEntry entry = new DirectoryEntry(LdapPath, domainAndUsername, password);
try
{
// Bind to the native AdsObject to force authentication.
Object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (null == result)
{
return false;
}
// Update the new path to the user in the directory
LdapPath = result.Path;
string _filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
Errmsg = ex.Message;
throw new Exception("Error authenticating user." + ex.Message);
}
}
I am generating assembly on runtime . Problem is that i cant get rid-of it after i am done with it. I need to destroy this assambly what i have created
string _Errors = "";
object[] parms = null;
CodeDomProvider _CodeCompiler = CodeDomProvider.CreateProvider("CSharp"); //new Microsoft.CSharp.CSharpCodeProvider().CreateCompiler();
CompilerParameters _CompilerParameters = new CompilerParameters();
_CompilerParameters.GenerateExecutable = false;
_CompilerParameters.GenerateInMemory = true;
_CompilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
_CompilerParameters.TreatWarningsAsErrors = false;
_CompilerParameters.CompilerOptions = "/optimize";
try
{
System.CodeDom.Compiler.CompilerResults _CompilerResults = null;
_CompilerResults = _CodeCompiler.CompileAssemblyFromSource(_CompilerParameters, _SourceCode);
if (_CompilerResults.Errors.Count > 0)
{
_Errors = "";
foreach (System.CodeDom.Compiler.CompilerError CompErr in _CompilerResults.Errors)
{
_Errors += "Line number " + CompErr.Line +
", Error Number: " + CompErr.ErrorNumber +
", '" + CompErr.ErrorText + ";\r\n\r\n";
}
return false;
}
else
{
_Errors = null;
} _CompilerResults.CompiledAssembly = null;
_CompilerResults = null;
_CompilerParameters = null;
_CodeCompiler.Dispose();
GC.Collect();}catch{}
I suppose by "get rid of it" you mean "unload". Unfortunatelly (or not so) once loaded into an AppDomain, assemblies cannot be unloaded. To circumvent this, you can generate (or load, for that matter) assembly in a separate AppDomain and then destroy it as needed.