Thursday, March 7, 2013

Searching Non-Microsoft LDAP Directories With .Net and Powershell

In this series on working with non-Microsoft directories in PowerShell and .Net

 
It is relatively straightforward to write .Net applications against Active Directory (AD) and Active Directory Lightweight Directory Services (ADLDS), but a number of challenges arise from writing applications against non-Microsoft directories.

More often than you would think, I am confronted with the use case of connecting to a non-Microsoft directory (ex. OpenLDAP, Oracle Internet Directory, IBM Directory Server, and Novell eDirectory). In a few cases, you can use the same classes that you would with AD or ADLDS that exist in the System.DirectoryServices namespace (ex. DirectoryEntry, DirectorySearcher, SearchResult, DirectoryEntries, etc), but in many cases you will run into an assortment of issues that usually ends in failure.

Digging into the namespace a little further, Microsoft has developed the System.DirectoryServices.Protocols namespace that gives you the ability to interact with LDAP directories at a lower level than the classes provided by the System.DirectoryServices namespace, but at a higher level than having to write your own LDAPv3 library from scratch.

Below is sample code for a sample C# .Net application and a sample Powershell script that allows interaction with non-Microsoft LDAP directories:






using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.DirectoryServices.Protocols;

using System.Collections;



namespace TestLDAPBind

{

    class Program

    {

        //Application Connects to and Searches OpenLDAP directory

        static void Main(string[] args)

        {

            // Connects to myopenldap.mikesblog.lan using SSL on a non-standard port

            LdapConnection c = new LdapConnection("directory.mikesblog.lan:637");

          

            //Set session options

            c.SessionOptions.SecureSocketLayer = true;

           

            // Pick Authentication type:

            // Anonymous, Basic, Digest, DPA (Distributed Password Authentication),

            // External, Kerberos, Msn, Negotiate, Ntlm, Sicily

            c.AuthType = AuthType.Basic;

           

           

            // Gets username and password. There are better ways to do this more securely...

            // but that's not the topic of this post.

            Console.Write("Enter Username: ");

            string username = Console.ReadLine();

           

            Console.WriteLine();



            Console.Write("Enter Password: ");

            string password = Console.ReadLine();



            // Bind with the network credentials. Depending on the type of server,

            // the username will take different forms. Authentication type is controlled

            // above with the AuthType

            c.Bind(new System.Net.NetworkCredential(username, password));



            SearchRequest r = new SearchRequest(

                //Base DN

                "ou=users,dc=mikesblog,dc=lan",

                //Filter

                "(uid=burrm)",

                //Search scope

                SearchScope.Subtree,

                //params string [] of attributes... in this case all

                "*");



            SearchResponse re = (SearchResponse)c.SendRequest(r);



            //How many results do we have?

            Console.WriteLine(re.Entries.Count);



            foreach (SearchResultEntry i in re.Entries)

            {

                //Do something with each entry here, such as read attributes

            }

         

        }

    }
}



The example Powershell port follows:

#Mike Burr
#Script Connects to and Searches OpenLDAP directory

#Load the assemblies
[System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")
[System.Reflection.Assembly]::LoadWithPartialName("System.Net")


#Connects to myopenldap.mikesblog.lan using SSL on a non-standard port
$c = New-Object System.DirectoryServices.Protocols.LdapConnection "myopenldap.mikesblog.lan:637"
          
#Set session options
$c.SessionOptions.SecureSocketLayer = $true;
           
# Pick Authentication type:
# Anonymous, Basic, Digest, DPA (Distributed Password Authentication),
# External, Kerberos, Msn, Negotiate, Ntlm, Sicily
$c.AuthType = [System.DirectoryServices.Protocols.AuthType]::Basic
           
# Gets username and password.
$user = Read-Host -Prompt "Username"
$pass = Read-Host -AsSecureString "Password"

$credentials = new-object "System.Net.NetworkCredential" -ArgumentList $user,$pass

# Bind with the network credentials. Depending on the type of server,
# the username will take different forms. Authentication type is controlled
# above with the AuthType
$c.Bind($credentials);

$basedn = "ou=users,dc=mikesblog,dc=lan"
$filter = "(uid=burrm)"
$scope = [System.DirectoryServices.Protocols.SearchScope]::Subtree
$attrlist = ,"*"

$r = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList `
                $basedn,$filter,$scope,$attrlist

#$re is a System.DirectoryServices.Protocols.SearchResponse
$re = $c.SendRequest($r);

#How many results do we have?
write-host $re.Entries.Count

foreach ($i in $re.Entries)
{
   #Do something with each entry here, such as read attributes


-->




5 comments:

  1. Hello - great work

    I was able to connect to the Oracle directory, how do I get the attribute values?
    results:
    PS C:\windows\system32> $re

    MatchedDN :
    Controls : {}
    ResultCode : Success
    ErrorMessage :
    Referral : {}
    References : {}
    Entries : {uid=99_greg, ou=people, ou=FIRMA, o=abc,c=pl}
    RequestId :


    PS C:\windows\system32> $re.Entries

    DistinguishedName Attributes Controls
    ----------------- ---------- --------
    uid=99_greg, ou=people, ou=FIRMA, o=abc,c=pl {ntuserdeleteaccount, cn, caid, beginofwork...} {}

    PS C:\windows\system32> $re | gm


    TypeName: System.DirectoryServices.Protocols.SearchResponse

    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj)
    GetHashCode Method int GetHashCode()
    GetType Method type GetType()
    ToString Method string ToString()
    Controls Property System.DirectoryServices.Protocols.DirectoryControl[] Controls {get;}
    Entries Property System.DirectoryServices.Protocols.SearchResultEntryCollection Entries {get;}
    ErrorMessage Property System.String ErrorMessage {get;}
    MatchedDN Property System.String MatchedDN {get;}
    References Property System.DirectoryServices.Protocols.SearchResultReferenceCollection References {get;}
    Referral Property System.Uri[] Referral {get;}
    RequestId Property System.String RequestId {get;}
    ResultCode Property System.DirectoryServices.Protocols.ResultCode ResultCode {get;}

    ReplyDelete
    Replies
    1. It is sort of a mess to get attributes. The basic process is that you have to convert the attribute value to the type that you need. In the C# example below, GetValues gets an object [] that you have to cast to the type that you are using.

      foreach (SearchResultEntry i in re.Entries)
      {
      string [] sn_string = (string [])i.Attributes["sn"].GetValues(typeof(String));

      foreach (string j in sn_string)
      {
      Console.WriteLine(j);
      }

      }

      In powershell, the way this works out is like this:

      $type_string = [System.Type]::GetType("System.String")

      foreach ($i in $re.Entries) {
      $sn_string = $i.Attributes["sn"].GetValues($type_string)

      foreach ($j in $sn_string)
      {
      Write-Host $j
      }
      }

      Hope this helps...

      Delete
  2. Mike - you're GREAT!

    Thank you very much! it works perfectly!

    is the first solution I've found on the net that really works

    ReplyDelete
  3. Mike - you described how to make a bind, search, view attributes

    Can you describe how to change the values ​​and save them to an LDAP (non $ MS)?

    :)

    ReplyDelete
    Replies
    1. Hi,

      Sorry it took a bit to get back to you (I was working on a project that took time away from my blog). I wrote a few posts that are linked that will help. For your specific question, see

      http://mikemstech.blogspot.com/2013/05/updating-attributes-in-non-microsoft.html

      Hope this helps

      Delete