How to read TermainsServices IADsTSUserEx Property from LDAP in C#? - c#

I have read the following properties from AD,
TerminalServicesProfilePath
TerminalServicesHomeDirectory
TerminalServicesHomeDrive
I've tried DirectoryEntry and DirectorySearcher. But they does not include the properties.
I found some example in vbscript and VC to read them.
However I failed to make it working in C#. Am I missing some tricky thing?
EDIT: Am I have to run it on "Windows Server" to make it works? Can it be read from win XP?

I think you can use the InvokeGet method on your DirectoryEntry, passing in the name of the property you want to read.
2008-12-10 11:50 CET — Edited in response to the comment
If I specify a garbage property name, I get the same COM exception. Are you sure the properties you're trying to retrieve are part of the AD schema?
Just to make sure, the code I'm using is as follows:
using (DirectorySearcher searcher = new DirectorySearcher("(cn=Test)"))
{
SearchResult result = searcher.FindOne();
if (result != null)
{
DirectoryEntry entry = result.GetDirectoryEntry();
string s = entry.InvokeGet("TerminalServicesHomeDrive") as string;
MessageBox.Show(s ?? "null");
}
}

I don't remember exactly, but it's something like this:
//user is a DirectoryEntry
IADsTSUserEx adsiUser = (IADsTSUserEx)user.NativeObject;
then you can get the TerminalServices properties you want via adsiUser.
From my experience you're better off developing on a Windows Server with access to AD due to the libraries you use. Then you'll probably make the above work, too :)

This works for me:
DirectoryEntry user = new DirectoryEntry("LDAP://" + sLDAP_SERVER + "/cn=" + SAMAccount + "," + sLdapFullPath, sUser, sPwd);
//ActiveDs.IADsUser iADsUser = (ActiveDs.IADsUser)user.NativeObject;
ActiveDs.IADsUser cont = null;
cont = user.NativeObject as ActiveDs.IADsUser;
TSUSEREXLib.IADsTSUserEx m_TsUser = (TSUSEREXLib.IADsTSUserEx)cont;
int m_TSLogonDisabled = 0;
m_TsUser.AllowLogon = m_TSLogonDisabled;

Related

Exception has been thrown by the target of an invocation ERROR when trying to changind AD user with ASP.net C#

I've tryed a lot of others solutions, and I still didn't make it work. Can someone help me please.
my code is like that:
I saw something about secureString, I tryed to use it but it still didn't work.
I saw too another solution that says to use var rather than string in the variables. Didn't work
I dont know if I'm doing something wrong or if those solutions that dosoen't work.
public bool RedefinirSenha(string pUsuario, string pSenhaAtual, string pNovaSenha)
{
var NovaSenha = pNovaSenha;
var SenhaAtual = pSenhaAtual;
var Usuario = pUsuario;
//string Pwd = String.Format(#"""{0}""", NovaSenha);
//byte[] pwdCerto = System.Text.Encoding.Unicode.GetBytes(Pwd);
try
{
string LDAP = myLDAPpath;
DirectoryEntry ADcon = new DirectoryEntry(LDAP, Usuario, SenhaAtual, AuthenticationTypes.Secure);
if (ADcon != null)
{
DirectorySearcher search = new DirectorySearcher(ADcon);
search.Filter = "(SAMAccountName=" + Usuario + ")";
SearchResult result = search.FindOne();
if (result != null)
{
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry != null)
{
try
{
userEntry.Invoke("ChangePassword", new object[] { SenhaAtual, NovaSenha }, AuthenticationTypes.Secure);
userEntry.Properties["LockOutTime"].Value = 0;
userEntry.CommitChanges();
userEntry.Close();
return true;
}
catch (Exception INex)
{
this.Erro = INex.Message + "COD:\r\n" + INex.InnerException;
userEntry.Close();
return false;
}
}
}
}
return true;
}
catch (Exception ex)
{
this.Erro = ex.Message;
return false;
}
}
First, there will be no difference at runtime if you declare the variables as var or string. Using the var keyword lets the compiler decide what the type is. Because you're assigning a string to it, then it is a string too. In most cases, var is fine. There are only very rare cases when you need to explicitly specify the type.
Second, DirectoryEntry.Invoke is defined like this:
public object Invoke (string methodName, params object[] args);
That may seem like you need to pass an object array, but that is not the case. The params keyword is a way to allow you to pass multiple parameters that get used inside the method as an array. So when you call it like this:
userEntry.Invoke("ChangePassword", new object[] { SenhaAtual, NovaSenha }, AuthenticationTypes.Secure);
The first parameter is an object array and the second parameter is AuthenticationTypes.Secure, then both of those get put inside the args array for use inside the Invoke method. But that is not what ChangePassword looks for. If this doesn't make sense to you, read the documentation for the params keyword and it should help.
When you call .Invoke("ChangePassword", ...), it calls the native Windows IADsUser.ChangePassword method. That takes two parameters: a string with the old password and a string with the new password - not an object array and an AuthenticationTypes value. So you should be calling it like this:
userEntry.Invoke("ChangePassword", SenhaAtual, NovaSenha);
You don't need to worry about the authentication because the password can only be changed over a secure connection. In the documentation, it says it behaves the same way as (IADsUser.SetPassword](https://learn.microsoft.com/en-ca/windows/win32/api/iads/nf-iads-iadsuser-setpassword), where it attempts several different ways to achieve a secure connection for you.
There is another way to change the password if the DirectoryEntry connection is already over a secure connection. A secure connection can either be using Kerberos, which can be done using AuthenticationTypes.Sealing (this is best if you are on the same network as the domain controller):
var ADcon = new DirectoryEntry(LDAP, Usuario, SenhaAtual, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
Or if by using LDAPS (LDAP over SSL), which you can use just by specifying port 636 in the LDAP path (this is the only way if you are not on the same network as the domain controller):
var ADcon = new DirectoryEntry("LDAP://example.com:636", Usuario, SenhaAtual);
If you do that, then you can change the password by updating the unicodePwd attribute directly, in the very specific way it wants it (enclosed in quotes and encoded in UTF-16), like this:
userEntry.Properties["unicodePwd"].Remove(Encoding.Unicode.GetBytes($"\"{SenhaAtual}\""));
userEntry.Properties["unicodePwd"].Add(Encoding.Unicode.GetBytes($"\"{NovaSenha}\""));
This should perform slightly faster since all of the work (changing the password and setting lockOutTime) is done over one network request instead of two.

C# code works from Console function but does not work in SQL CLR Stored Procedure

PLEASE HELP!!! I have a code to get data from AD. This used to work in SQL2014/Visual Studio2013. Now, we are migrating to SQL2016. I tested the code in a Console App and it worked just fine. It just does not work when I create the same code into a SQL CLR Stored Proc using Visual Studio 2017.
This is the code in the Console App:
static void Main(string[] args)
{
DirectoryEntry ldapConnection;
DirectorySearcher search;
SearchResult result;
DirectoryEntry ldapconnection = null;
string szJDEID = "";
string szErrorMessage = "";
string szNetworkID = "xyz";
//--- Create and return new LDAP connection with desired settings
ldapConnection = new DirectoryEntry("LDAP://DC.company.com:389", "userid", "password");
ldapConnection.Path = "LDAP://DC=company,DC=com";
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
//--- create search object which operates on ldap connection object and set search object to only find the user specified
search = new DirectorySearcher(ldapconnection);
search.Filter = "(&(samaccountname=" + szNetworkID.Trim() + ")(memberOf=CN=JDEdwards Users,OU=Mail Enabled Manual,OU=Groups,OU=Global,DC=company,DC=com))";
result = search.FindOne();
if (result != null)
{
//--- create an array of properties that we would like and add them to the search object
string[] requiredproperties = new string[] { "extensionattribute13" };
foreach (string property in requiredproperties)
search.PropertiesToLoad.Add(property);
result = search.FindOne();
if (result != null)
{
foreach (string property in requiredproperties)
foreach (object mycollection in result.Properties[property])
szJDEID = mycollection.ToString();
}
}
else
{
szErrorMessage = "ERROR: This user does not belong to the JDEdwards Users AD Group. Please check with the IT Helpdesk team.";
}
}
I get the value of szJDEID as stored in Extension Attribute13. When I put the same code in a SQL CLR Stored Proc, the Logic always returns the szErrorMessage value.
What am I missing?
Thanks in advance.
Finally. As you had rightly pointed earlier, the bindDn was incorrect. The issue is we moved from one DC to another. I used lip.exe to find out the principal - userid#domain.com. Also, the ldapConnection.Path was not needed anymore as it was incorrect with the new DC. So, finally, it is working. Thanks Clint.

Accessing/parsing "msDS-AllowedToActOnBehalfOfOtherIdentity" AD property in C#

I have a need to manage Kerberos Resource Based Delegation in C# (I know it's easier in Powershell but that is not the requirement). The attribute on the user/computer/service accounts is msDS-AllowedToActOnBehalfOfOtherIdentity, but this seems to be some COM object which I can't seem to deal with in C#:
static void Main(string[] args)
{
string ou = #"OU=some,OU=ou,DC=corp,DC=com";
string cn = #"someaccount";
DirectoryEntry de = new DirectoryEntry();
de.Username = #"CORP\userwithOUrights";
de.Password = #"password";
de.AuthenticationType = AuthenticationTypes.Secure;
de.Path = $"LDAP://CN={cn},{ou}";
Object a = de.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"];
}
After this, a doesn't seem to be anything I can do much with, unlike other properties. It is some COM object and I need to get the accounts which are in there. Powershell reports that this property returns a System.DirectoryServices.ActiveDirectorySecurity object and I see useful methods in this class for decoding the binary format which is stored in AD etc. But this does not seem to be the return type from the property call in C#.
Update: All of this is now better documented in an article on my website: Handling NT Security Descriptor attributes
According to this the "attribute syntax" for that attribute is 2.5.5.15. According to this, that means it's a "String(NT-Sec-Desc)". According to this, that means it's a IADsSecurityDescriptor COM object.
You can add a COM reference in your project to "Active DS Type library" and cast it directly to IADsSecurityDescriptor, like this:
var act = (ActiveDs.IADsSecurityDescriptor)
de.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"].Value;
Console.WriteLine(act.Owner);
The Owner property gives you a DOMAIN\Username.
According to this random code I found, it seems you can also use the RawSecurityDescriptor class to interact with it. There is a constructor that takes a plain string, but you also can't seem to get the raw string from the attribute from DirectoryEntry.
But I did remember that sometimes DirectorySearcher will give you values in a different type than DirectoryEntry (doesn't make sense, but it's true). That appears to be true here. DirectorySearcher gives this attribute to you as a byte[], and RawSecurityDescriptor does have a constructor that takes a byte[].
So it seems you can do something like this:
string ou = #"OU=some,OU=ou,DC=corp,DC=com";
string cn = #"someaccount";
var search = new DirectorySearcher(new DirectoryEntry($"LDAP://{ou}"), $"(cn={cn})");
search.PropertiesToLoad.Add("msDS-AllowedToActOnBehalfOfOtherIdentity");
var result = search.FindOne();
var act = new RawSecurityDescriptor(
(byte[]) result.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"][0], 0);
Console.WriteLine(act.Owner);
//make changes to act.DiscretionaryAcl
byte[] descriptor_buffer = new byte[act.BinaryLength];
act.GetBinaryForm(descriptor_buffer, 0);
var de = result.GetDirectoryEntry();
de.Properties["msDS-AllowedToActOnBehalfOfOtherIdentity"].Value = descriptor_buffer;
de.CommitChanges();
In this, act.Owner is an account SID.

C# - Get Direct Reports under another manager's Direct Reports list

I've been trying to figure out how to do this but I'm always met with a bump on the road. What I'm trying to do is to get the reporting people under my manager's direct reports list; so for example, "Alex" is a direct report under my manager, however, when you go into his organization you see that he also has direct reports that report directly to him - I am trying to get "those" reports not only from his side but from anyone else in the list that has direct reports as well. What is needed for me to effectively execute that idea? Many thanks!
This is my code to only get Direct Reports under my manager tree:
public void GetManagerDirectReports()
{
Application App = new Application();
AddressEntry currentUser = App.Session.CurrentUser.AddressEntry;
if (currentUser.Type == "EX")
{
ExchangeUser manager = currentUser.GetExchangeUser().GetExchangeUserManager();
if (manager != null)
{
AddressEntries addrEntries = manager.GetDirectReports();
if (addrEntries != null)
{
foreach (AddressEntry addrEntry in addrEntries)
{
ExchangeUser exchUser = addrEntry.GetExchangeUser();
StringBuilder sb = new StringBuilder();
sb.AppendLine("Name: "
+ exchUser.Name);
sb.AppendLine("Title: "
+ exchUser.JobTitle);
sb.AppendLine("Department: "
+ exchUser.Department);
sb.AppendLine("Email: "
+ exchUser.PrimarySmtpAddress);
Debug.WriteLine(sb.ToString());
Console.WriteLine(sb.ToString());
Console.ReadLine();
}
}
}
}
}
I opted to go ahead and use LDAP instead of Microsoft's EWS because I saw it uses _ComObject and I don't believe that will work with what I need it to work for. Essentially, I created a master load class and then a sub-class to handle LDAP syntax which would give me the emails of all managers who have direct reports. Something I found useful while doing my research is this filter string which came in quite handy in my time of need (where "cn" is the manager's name):
searcher = new DirectorySearcher
{
Filter = "(&(objectClass=user)(objectCategory=person)(manager=" + cn + ",OU=Unit,OU=People,DC=my,DC=domain,DC=com))"
};
searcher.PropertiesToLoad.Add("DirectReports");
searcher.PropertiesToLoad.Add("mail");
Hope this can serve of some use to coming questions related to this in the future.

Connect to LDAP server

I'd like to implement a function that searches an ldap server (name, phone number etc.)
Here's what I wrote (the server address is a phony one, but the real one has the same pattern)
DirectoryEntry de = new DirectoryEntry("LDAP://aet7ldap.phony.com")
DirectorySearcher ds = new DirectorySearcher(de);
var test = ds.FindAll();
I know that there are other constructors (string path, string user, string password), but I don't know my username or password and I'm not sure if I need one. So please help me figure out how to do it without these parameters (if that is possible).
I've tried to write a filter as well but that's another thing, because first I need to get my connection right. But can I assume that I have to use these parameters (or column names?) I keep reading everywhere (such as 'gn' for given name and so on)?
sounds like you're trying to connect to Active Directory using the Directory entry method to find a user, you haven't mentioned if you're doing anything to the user you're looking for once they're found, so I'll just give you code that finds the user for you.
using(DirectoryEntry de = new DirectoryEntry("LDAP://servername/DC=phony,DC=com"))
using(DirectorySearcher ds = new DirectorySearcher(de))
{
ds.Filter="(&(objectClass=user)(sAMAccountName="username"))";
//I don't know exactly what criteria you're using to find the user
ds.Filter="(&(objectClass=user)(distinguishedname="")(givenname=""))"
ds.SearchScope = SearchScope.Subtree;
//performing the search and assigning the result to result
SearchResult result = ds.FindOne();
if (result != null)
{
using(DirectoryEntry user = result.GetDirectoryEntry())
{
//put code here to deal with the user as you see fit.
}
lblOutput.Text = "User " + userName + " was found.";
}
}
The Filter is the most important part to find the user you're looking for, the & means and so in the first example I gave in the code above, you're looking for an object with the class user AND the username username. Its easy enough to figure out. Here is a link to a list of all Active Directory attributes, you should be able to find what you're looking for there.
http://www.selfadsi.org/user-attributes.htm
Regards,
Tory Hill

Categories