c# against Active Directory over LDAP - c#

I'm coding some c# against Active Directory and have tried endlessly to get this to work to no avail. The following code works and the code that follows it does not:
The code below is using "WinNT://" + Environment.MachineName + ",Computer" to make the connection and works fine.
DirectoryEntry localMachine = new DirectoryEntry
("WinNT://" + Environment.MachineName + ",Computer");
DirectoryEntry admGroup = localMachine.Children.Find
("Administrators", "group");
object members = admGroup.Invoke("members", null);
foreach (object groupMember in (IEnumerable)members)
{
DirectoryEntry member = new DirectoryEntry(groupMember);
output.RenderBeginTag("p");
output.Write(member.Name.ToString());
output.RenderBeginTag("p");
}
base.Render(output);
I'm now trying to change the line:
"WinNT://" + Environment.MachineName + ",Computer"
to
"LDAP://MyDomainControllerName"
but it seems no matter what value I try in place of the value 'MyDomainControllerName' it wont work.
To get the 'MyDomainControllerName' value I right clicked on MyComputer and copied the computer name value as suggested elsewhere but this didn't work.
When I try using the LDAP://RootDSE option above it results in the following error:
The Active Directory object located at the path LDAP://RootDSE is not a container
Is this a problem with the member methods as you mention?

Yes- RootDSE is not a container - but it holds a number of interesting properties which you can query for - e.g. the name of your domain controller(s).
You can check these out by using code like this:
DirectoryEntry deRoot = new DirectoryEntry("LDAP://RootDSE");
if (deRoot != null)
{
Console.WriteLine("Default naming context: " + deRoot.Properties["defaultNamingContext"].Value);
Console.WriteLine("Server name: " + deRoot.Properties["serverName"].Value);
Console.WriteLine("DNS host name: " + deRoot.Properties["dnsHostName"].Value);
Console.WriteLine();
Console.WriteLine("Additional properties:");
foreach (string propName in deRoot.Properties.PropertyNames)
Console.Write(propName + ", ");
Console.WriteLine();
}
Or save yourself the trouble and go grab my "Beavertail ADSI Browser" in C# source code - shows in detail how to connect to RootDSE and what it offers.

When connecting to AD using the .NET Framework, you can use "serverless" binding or you can specify a server to use everytime (server bound).
Here's an example of using both:
// serverless
DirectoryEntry rootConfig = new DirectoryEntry("LDAP://dc=domainname,dc=com");
// server bound
DirectoryEntry rootEntry = new DirectoryEntry("LDAP://domainControllerName/dc=domainName,dc=com");
I think where you were going astray is you forgot to include the FQDN for your domain on the end. Hope this helps.

You need to pass it an authorized Username and password.
try setting: DirectoryEntry.Username and DirectoryEntry.Password

have you tried speciying the port number and other parms?
Our ldap string looks like: LDAP://myserver:1003/cn=admin#xyz.com|1,ou=Members,o=mdhfw2

It looks like you need to get the LDAP connection information. You can call LDAP://RootDSE to get the information as shown in the ASP.NET Wiki.
Please keep in mind that the LDAP objects do not have the same member methods and properties as the WINNT objects, so do not expect the group.Invoke("members") and other functions to work exactly the same. You should read up on the DirectoryServices documentation with LDAP as well.

Related

How to get BaseDN of Directory Server from IP or serverName

I am working on LDAP, I have installed OpenLDAP on my local machine and created a RootDSE as dc=wave,dc=com.
and I am trying to fetch this baseDN using this code:
DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}", server), "CN=Manager,dc=wave,dc=com", "secret");
string dnPath;
try
{
dnPath = directoryEntry.Properties["distinguishedName"].Value.ToString();
}
catch
{
dnPath = "";
}
but it always fall in exception, also why do I need to pass whole dn for username?
same thing if I try with Active Directory server it works perfectly, also I don't need to pass the whole dn for username.
any Idea?
The System.DirectoryServices namespace really only works with Active Directory. You should switch over to using System.DirectoryServices.Protocols when working with any other directory. You'll probably find that its even better when working with AD as well.

Change Local user settings from within c#

I need to write a program that will take a existing local windows user, change the "Start the following program at logon field" in their environment tab and change the password to a new password.
I am using local users instead of AD, will System.DirectoryServices work? Also any tutorials on using System.DirectoryServices would be very helpful.
EDIT --
Ok, so I can find the existing user and I know how to update it. However I do not know what to put in the ???. What AD property represents "Start the following program at logon field" in their environment tab on the user settings page?
DirectorySearcher search = new DirectorySearcher();
search.Filter = String.Format("(SAMAccountName={0})", prams.user);
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if (result == null)
{
Console.Error.WriteLine("No user of name {0} in the system", prams.user);
return -2;
}
DirectoryEntry entry = result.GetDirectoryEntry();
entry.InvokeSet("???", new object[] { newProgramLognField });
EDIT two---
I now know that the field I need is "Parameters" however I this term is very hard to search for in the MSDN because it is such a generic term. I think I have the correct one because when i run the code
DirectoryEntry entry = new DirectoryEntry("WinNT://testComputer/testUser");
Console.WriteLine("connected");
string value = (string)entry.Properties["Parameters"].Value;
Console.WriteLine(value);
I get the result
P☺CtxCfgPresent????☺CtxCfgFlags1
????☺CtxShadow????*☻☺CtxMinEncryptionLevel? #☺CtxWorkDirectory??????????????????
??????????????"C☺CtxInitialProgram??????????????????????????????????????????????
????????????????????????????????????????????????????????????????????????????????
????????
I am guessing that InitalProgram portion is close to what I want, most likely the ??? after is is the existing path that is there but has be mucked up.
Now the question is how do I write correctly to it?
Edit-3
I try changing the casting from a string to a PropertyValueCollection however that just gives me the error at run time
Unhandled Exception: System.InvalidCastException: Unable to cast object of type
'System.String' to type 'System.DirectoryServices.PropertyValueCollection'.
at UserUpdater.Program.Main(String[] args) in e:\Visual Studio 2008\Projects\
UserUpdater\UserUpdater\Program.cs:line 91
My copy of the TechNet Script Repository says that this WSH script can be used to change the password.
Set objComputer = GetObject _
("LDAP://CN=atl-dc-01,CN=Computers,DC=Reskit,DC=COM")
objComputer.SetPassword "whatever"

Connecting to LDAP from C# using DirectoryServices

I am trying to connect to an edirectory 8.8 server running LDAP. How would I go about doing that in .Net? Can I still use the classes in System.DirectoryService such as DirectoryEntry and DirectorySearcher or are they AD specific? Do I need to specify the "Connection String" any differently?
I am trying something like the code below but it doesn't seem to work...
DirectoryEntry de = new DirectoryEntry ("LDAP://novellBox.sample.com","admin","password",AuthenticationTypes.None);
DirectorySearcher ds = new DirectorySearcher(de);
var test = ds.FindAll();
Any ideas?
Well, I think your connection string is missing a bit - specifying just the server name isn't good enough - you also need to specify a "starting point" for your search.
In AD, this would typically be something like the "Users" container in your domain, which you'd specify like this in LDAP parlance:
LDAP://novellBox.sample.com/cn=Users,dc=YourCompany,dc=com
Not sure how LDAP compliant the newer versions of eDirectory are - but that should work since in theory, it's standard LDAP regardless of the implementation :-)
But then again: only in theory, there's no difference between theory and practice.....
There's also a System.DirectoryServices.Protocols namespace which offers low-level LDAP calls directly - and that's definitely not tied to AD at all, but it's really quite low-level.....
There's also a Novell C# LDAP library but I've never tried it and can't say how complete or capable it is. It might give you some clues, though!
Also see this other Stackoverflow question about Novell, LDAP and C# - it might give you additional info.
I had a hard time figuring this out but you could use something like the following, it worked sweet for me:
Domain domain = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, "novellBox.sample.com");
DirectorySearcher ds = new DirectorySearcher(domain.GetDirectoryEntry(), searchQuery);
using (SearchResultCollection src = ds.FindAll())
{....}
I think you need to use LDAP syntax for the host.
Make sure you don't forget to release the connection with using - if you don't dispose of the directory entries they hang around forever until the pool runs out and your app breaks.
using (DirectoryEntry de = new DirectoryEntry ("LDAP://CN=server,DC=domain,DC=com","admin","password",AuthenticationTypes.Secure))
{
...
}
Depending ont he directory server configuration, you might actually need to use the System.DirectoryServices.Protocols namespace. I wrote up a post on connecting to OpenLDAP with it.
http://mikemstech.blogspot.com/2013/03/searching-non-microsoft-ldap.html
If the external LDAP require authentication with DN try this: first retrieve the DN of user, then try the authentication with DN and user credentials. I've tested it on Domino LDAP.
// Autheticate in external LDAP
string ldapserver = "10.1.1.1:389";
string ldapbasedn = "o=mycompany";
string ldapuser = "cn=Administrator,o=mycompany";
string ldappassword = "adminpassword";
string ldapfilter = "(&(objectclass=person)(cn={0}))";
string user = "usertest";
string password = "userpassword";
try
{
string DN = "";
using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + ldapserver + "/" + ldapbasedn, ldapuser, ldappassword, AuthenticationTypes.None))
{
DirectorySearcher ds = new DirectorySearcher(entry);
ds.SearchScope = SearchScope.Subtree;
ds.Filter = string.Format(ldapfilter, user);
SearchResult result = ds.FindOne();
if (result != null )
{
DN = result.Path.Replace("LDAP://" + ldapserver + "/" , "");
}
}
// try logon
using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + ldapserver + "/" + ldapbasedn, DN, password, AuthenticationTypes.None))
{
DirectorySearcher ds = new DirectorySearcher(entry);
ds.SearchScope = SearchScope.Subtree;
SearchResult result = ds.FindOne();
}
} catch (Exception) { }
I am trying to connect to an edirectory 8.8 server running LDAP. How would I go about doing that in .Net? Can I still use the classes in System.DirectoryService such as DirectoryEntry and DirectorySearcher or are they AD specific?
We are using System.DirectoryServices for Microsoft Active Directory, OpenLDAP running on Linux and eDirectiry without any problem. So the answer is yes, you can use these classes to access eDir.
Do I need to specify the "Connection String" any differently?
Yes you are. When passing to DirectoryEntry a string starting with "LDAP://" you need to conform to the LDAP syntax which is very different than URI syntax.
I recommend you to use an LDAP browser (google it, there are many free downloads) in order to get the correct path to the root object otherwise you will spend time on trying to figure out the correct object types.

How can I convert from a SID to an account name in C#

I have a C# application that scans a directory and gathers some information. I would like to display the account name for each file. I can do this on the local system by getting the SID for the FileInfo object, and then doing:
string GetNameFromSID( SecurityIdentifier sid )
{
NTAccount ntAccount = (NTAccount)sid.Translate( typeof( NTAccount ) );
return ntAccount.ToString();
}
However, this does not work for files on a network, presumably because the Translate() function only works with local user accounts. I thought maybe I could do an LDAP lookup on the SID, so I tried the following:
string GetNameFromSID( SecurityIdentifier sid )
{
string str = "LDAP://<SID=" + sid.Value + ">";
DirectoryEntry dirEntry = new DirectoryEntry( str );
return dirEntry.Name;
}
This seems like it will work, in that the access to "dirEntry.Name" hangs for a few seconds, as if it is going off and querying the network, but then it throws a System.Runtime.InteropServices.COMException
Does anyone know how I can get the account name of an arbitrary file or SID? I don't know much about networking or LDAP or anything. There's a class called DirectorySearcher that maybe I'm supposed to use, but it wants a domain name, and I don't know how to get that either - all I have is the path to the directory I'm scanning.
See here for a good answer:
The best way to resolve display username by SID?
The gist of it is this bit:
string sid="S-1-5-21-789336058-507921405-854245398-9938";
string account = new System.Security.Principal.SecurityIdentifier(sid).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
This approach works for me for non-local SID's over the active directory.
The SecurityReference object's Translate method does work on non-local SIDs but only for domain accounts. For accounts local to another machine or in a non-domain setup you would need to PInvoke the function LookupAccountSid specifying the specific machine name on which the look up needs to be performed.
System.DirectoryServices.AccountManagement.UserPrincipal class (msdn link) has a static function FindByIdentity to convert an SID to a User object. It should be able to work both against the local machine or an LDAP/Active Directory server. I have only used it against active directory.
Here is an example that I have used in IIS:
// Set the search context to a specific domain in active directory
var searchContext = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=SomeOU,DC=YourCompany,DC=com");
// get the currently logged in user from IIS
MembershipUser aspUser = Membership.GetUser();
// get the SID of the user (stored in the SecurityIdentifier class)
var sid = aspUser.ProviderUserKey as System.Security.Principal.SecurityIdentifier;
// get the ActiveDirectory user object using the SID (sid.Value returns the SID in string form)
var adUser = UserPrincipal.FindByIdentity(searchContext, IdentityType.Sid, sid.Value);
// do stuff to user, look up group membership, etc.
In C#, get the user SID and assign it to a string variable through:
string strUser = System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString();
You will need to use string because the ability to resolve to the UserName supports string. In other words, using var varUser will result in a namespace error.
string strUserName = new System.Security.Principal.SecurityIdentifier(strUser).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
You can also get account name of special accounts like "Everyone" with code like this that will work regardless of user's language settings:
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
string everyone = everyoneSid.Translate(typeof(System.Security.Principal.NTAccount)).ToString();
Ooh, then it's possible that the LDAP call is not working because you might not be in an Active Directory environment. If this is the case, then each of your machines is responsible for its own identity store. And your first code sample is not working across the network because the machine on which you are executing your code does not know how to resolve the SID that only makes sense on the remote machine.
You really should check if your machines are a part of an Active Directory. You would know this during the logon process. Or you can check by right clicking on "My Computer", select "Properties", the "Computer Name" tab, then see if your computer is part of a domain.
Great. I cribbed some LookupAccountSid() code from here:
http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid
And that worked, though I had to provide the host name myself. In the case of a UNC path I can just take the first component of it. When it's a mapped drive, I use this code to convert the path to a UNC one:
http://www.wiredprairie.us/blog/index.php/archives/22
It seems to work, so that's how I'll do it, unless someone comes up with a situation in which the first component of a UNC path isn't the host name...
Thank you all for your help.
This one is a stumper. You are in an Active Directory environment right? Just checking:)
Anyhow, instead of binding with sid.Value,
string str = "LDAP://<SID=" + sid.Value + ">";
I would try converting the SID's byte array to an Octet String and bind with that instead.
There is a sweet example here on page 78. This will get you closer. To be honest, I've not tried binding with a SID before. But I've had success binding with a user's GUID though :)
Good luck and let me know how it goes.
Get the current domain:
System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
Get a directory entry from ldap and the domain name:
DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://{0}", domain));
Get the sid from an ActiveDirectoryMembershipProvider ActiveDirectoryMembershipUser:
ActiveDirectoryMembershipUser user = (ActiveDirectoryMembershipUser)Membership.GetUser();
var sid = (SecurityIdentifier)user.ProviderUserKey;
Get the username from the SecurityIdentifier:
(NTAccount)sid.Translate(typeof(NTAccount));
Get directory search done on an activedirectory with the domain directory entry and username:
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = string.Format("(SAMAccountName={0})", username);
search.PropertiesToLoad.Add("Name");
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("company");
search.PropertiesToLoad.Add("homePhone");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("givenName");
search.PropertiesToLoad.Add("lastLogon");
search.PropertiesToLoad.Add("userPrincipalName");
search.PropertiesToLoad.Add("st");
search.PropertiesToLoad.Add("sn");
search.PropertiesToLoad.Add("telephoneNumber");
search.PropertiesToLoad.Add("postalCode");
SearchResult result = search.FindOne();
if (result != null)
{
foreach (string key in result.Properties.PropertyNames)
{
// Each property contains a collection of its own
// that may contain multiple values
foreach (Object propValue in result.Properties[key])
{
outputString += key + " = " + propValue + ".<br/>";
}
}
}
Depending on the data in your active directory, you will get a varied response in the output.
Here is a site that has all the user properties I needed:
For all the Windows developers, the answer is LookupAccountSid
LookupAccountSid(null, Sid, username, userSize, domainName, domainSize, sidType);
I am quite sure you will be able to use the accepted answer from here: Determine the LocalSystem account name using C#
Basically, you can translate an instance of the SecurityIdentifier class to type NTAccount, from which you can get the user name. In code:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);

Determine the LocalSystem account name using C#

We have an application that installs SQL Server Express from the command line and specifies the service account as the LocalSystem account via the parameter SQLACCOUNT="NT AUTHORITY\SYSTEM".
This doesn't work with different languages because the account name for LocalSystem is different. There's a table listing the differences here:
http://forums.microsoft.com/MSR/ShowPost.aspx?PostID=685354&SiteID=37
This doesn't seem to be complete (the Swedish version isn't listed). So I'd like to be able to determine the name programmatically, perhaps using the SID?
I've found some VB Script to do this:
Set objWMI = GetObject("winmgmts:root\cimv2")
Set objSid = objWMI.Get("Win32_SID.SID='S-1-5-18'")
MsgBox objSid.ReferencedDomainName & "\" & objSid.AccountName
Does anyone know the equivalent code that can be used in C#?
You can use .NET's built-in System.Security.Principal.SecurityIdentifier class for this purpose: by translating it into an instance of NtAccount you can obtain the account name:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);
Later edit, in response to question in comments: you do not need any special privileges to do SID-to-name lookups on the local machine -- for example, even if the user account you're running under is only in the Guests group, this code should work. Things are a little bit different if the SID resolves to a domain account, but even that should work correctly in most cases, as long as you're logged on to the domain (and a domain controller is available at the time of the lookup).
Or you can use:
string localSystem = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null).Translate(typeof(NTAccount)).Value;
With WellKnownSidType you can look for other accounts, as NetworkService for example.
This should do something similar to what you posted. I'm not sure how to get specific properties of WMI objects offhand, but this will get you started with the syntax:
ManagementObject m = new ManagementObject("winmgmts:root\cimv2");
m.Get();
MessageBox.Show(m["Win32_SID.SID='S-1-5-18'"].ToString());
The problem with the accepted answer is that the account name must be resolvable by the local machine running the code.
If you are reading the ACLs on a remote machine you may well not be able to resolve Domain SIDs / local SIDs on the remote box. The following uses WMI and takes the parameter of the remote machine and the SID you want the remote machine to resolve.
/// <summary>
/// Returns the Account name for the specified SID
// using WMI against the specified remote machine
/// </summary>
private string RemoteSID2AccountName(String MachineName, String SIDString)
{
ManagementScope oScope = new ManagementScope(#"\\" + MachineName +
#"\root\cimv2");
ManagementPath oPath = new ManagementPath("Win32_SID.SID='" + SIDString + "'");
ManagementObject oObject = new ManagementObject(oScope, oPath, null);
return oObject["AccountName"].ToString();
}

Categories

Resources