Using System.DirectoryServices.Protocols from Powershell

This simple Powershell module demonstrates how to use robust and powerfull objects from System.DirectoryServices.Protocols (S.DS.P) from Powershell.

S.DS.P.zip
 
 
 
 
 
4.5 Star
(6)
9,795 times
Add to favorites
Active Directory
10/20/2019
E-mail Twitter del.icio.us Digg Facebook
Sign in to ask a question


  • Is this usable with a non-AD LDAP Directory?
    1 Posts | Last post October 09, 2019
    • I need to connect to a non-AD directory using LDAP to query for various attributes and then act upon them with powershell. I should mention I am a novice powershell user. I am quite familiar with accessing AD and non-AD LDAP directories using unix LDAP utilities (ldapsearch, ldapmodify etc.) The non-AD directory does not allow anonymous BINDs.
      
      I'm trying this from the powershell commandline:
      
      $cred=new-object System.Net.NetworkCredential("cn=dmanager","test1234","")
      
      $Ldap="-Credential $cred"
      
      $MyConnection=Get-LdapConnection -LdapServer:10.0.14.124 -Port 1389 
      
      Find-LdapObject $Ldap -LdapConnection:$MyConnection -SearchFilter:"(&(userflag=*)(objectClass=testers))" -SearchBase:"o=test.net" -PropertiesToLoad:@("dn","uid","userflag")
      
      I get this error:
      
      Find-LdapObject : Cannot process argument transformation on parameter 'searchScope'. Cannot convert value "-Credential
      System.Net.NetworkCredential" to type "System.DirectoryServices.Protocols.SearchScope". Error: "Unable to match the
      identifier name -Credential System.Net.NetworkCredential to a valid enumerator name. Specify one of the following
      enumerator names and try again:
      Base, OneLevel, Subtree"
      At line:1 char:17
      + Find-LdapObject $Ldap -LdapConnection:$MyConnection -SearchFilter:"(& ...
      +                 ~~~~~
          + CategoryInfo          : InvalidData: (:) [Find-LdapObject], ParameterBindingArgumentTransformationException
          + FullyQualifiedErrorId : ParameterArgumentTransformationError,Find-LdapObject
      
      
      According to the notes in the example for this query the default searchscope is "subtree". 
      
      If I add a -SearchScope:"SubTree" parameter, the error changes:
      
      Exception calling "SendRequest" with "2" argument(s): "A local error occurred."
      At C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Find-LdapObject\S.DS.P.psm1:265 char:13
      +             $rsp = $LdapConnection.SendRequest($rq, $Timeout) -as [Sy ...
      
      
  • I noticed in latest version in from GIT repository that the Find-LdapObject has bug
    4 Posts | Last post September 02, 2019
    • Nice script, 
      
      It seems you should be setting $vals=$null at beginning of foreach loop for $PropertiesToLoad
      Otherwise, properties that do not exist in particular response are getting assigned the previous iteration of the loop's value.
      
                          #load properties of object, if requested, using ranged retrieval
                          foreach ($attrName in $PropertiesToLoad) {
                              $start=-$rangeSize
                              $lastRange=$false
                              $vals=$null
      
      (Adjusting it like so results in thrown exception, for those properties that are requested, but do not exist in the targetted repository (so maybe should be switched to a warning instead) but this change does not stop the overall property list from being updated with your current code.
      
    • the last part should have read 
      " but this proposed change does not stop the overall property list from being retrieved based on your latest code. You just will instead receive "Object not set to an instance of an object" for the null property values that did not exist on the requested object being returned. (instead of Attributes that should be null, getting the last loop iteration's Attribute value).  
      
    • I was at first testing this function for completely bogus attributes that did not exist in schema and ran across this issue, but then I also noticed it when in a instance where the attributes I was requesting were valid in the schema, but that they just did not appear on every object being returned in the collection  being returned (I querying an old ADAM repo).  
      
      
    • Hello, thanks for reporting. Please check v1.9.7 on GitHub, shall be fixed
      
      Thank you,
      Jiri
      
  • How to use multiple LDAP (DCs) for redundancy
    2 Posts | Last post May 02, 2019
    • Hi Jiri,
      
      First I would like to thank you for this great module. I use it in a domain login script to fetch some user attributes.
      
      In order to get some redundancy I would like to add multiple LDAP servers in the script. At the moment I use something like this to initialize the LDAP connection:
      
      $MyConnection=Get-LdapConnection -LdapServer:dc1.domain.local -EncryptionType Kerberos
      
      Is there an easy way to add a list of LDAP servers and connect to the next available in case the primary dc1 is not available ?
      
      Thank you for your kind help.
      
      Best regards
      Andreas
    • Hello Andreas,
      apologies for late answer - recently I've been monitoring GitHub where you can ask questions, too.
      Easy way to achieve HA just by using domain DNS name (such as domain.local) as LdapServer name and let runtime to reach domain controller - works very well
      
      Hope this helps,
      Jiri
  • Adding and removing members from groups
    3 Posts | Last post November 04, 2018
    • Hi Jiri, I've successfully tested the module with non-AD LDAP server to replace a single member of a group but the capability to add additional members does not appear to be available 'out of the box'. I've read through 'Introduction to System.DirectoryServices.Protocols (S.DS.P)' at https://msdn.microsoft.com/en-us/library/bb332056.aspx and I see the advice to use ModifyRequest object with PermissiveModifyControl but I lack the skills to translate this into PowerShell so I can edit the Edit-LdapObject function. Are you able to provide some advice please?
    • Sorry! Just a few minutes after posting this question, I figured it out without any modification to the module/function. In case anyone else needs to know, when you build the object for the Edit-LdapObject function, you just need to define the member attribute in the hash table and then use an array of values for that attribute like this:
      
      $Props = @{"distinguishedName"=$null;instData=$null;uniqueMember=$null}
      $obj = new-object PSObject -Property $Props
      $obj.DistinguishedName = "cn=testgroup,ou=Groups,dc=sys,dc=com"
      $obj.instData = "this is a description"
      $obj.uniqueMember = "uid=testuser1,ou=People,dc=sys,dc=com", "uid=testuser2,ou=People,dc=sys,dc=com", "uid=testuser3,ou=People,dc=sys,dc=com"
      
      Edit-LdapObject -LdapConnection:$conn -Object $obj
    • Hi Dan,
      yes, you made it working the way how it's supposed to work.
      you may have noticed that Edit-LdapObject now has 'Replace' operation hardcoded. This is not limitation for single-valued properties, but for multi-valued properties, with pre-existing values (such as group members), it actually requires to load all values first (i.e. via Find-LdapObject), then add/remove values in-memory as needed, and then use Edit-LdapObject to place the result back.
      For large set of values, this may be ineffective; in vNext I may come with better option that allows more effective operations over multival props
      
      Hope this helps,
      Jiri
  • Can you control client side timeout
    2 Posts | Last post November 04, 2018
    • Hello Jiri
      First of all , let me say that your script was a huge help in getting a more powershell documented (technical) version of S.DS.P.
      Secondly , is there a way of controlling the client side timeout limit by a S.DS.P object ? 
    • Hello,
      client side timeout is controlled by Timeout parameter of Get-LdapObject cmdlet, and applies to all requests sent over that connection.
      then there's server side timeout controlled by Timeout property of Find-LdapObject cmdlet. It tells the server how to long it's allowed to process the search request.
      
      Hope this helps, please let me know your experience with timeouts in the use case you're working on.
      
      Thanks,
      Jiri
  • I am unable to get this script to work.
    10 Posts | Last post September 24, 2018
    • When I try to run it, I get an error "Find-LdapObject : Unable to find type [System.DirectoryServices.Protocols.LdapConnection]."  I'm running Windows 10 with .NET 4.6.1.   
      
      How do I get PowerShell to 'find' this library?  
    • Hello,
      this happens automatically when you import the module S.DS.P - dependency on assembly is specified in module manifest file (.psd1) and Powershell automatically loads dependencies.
      I just tested on my Win10 machine and works as expected
      
      Hope this helps,
      Jiri
    • Thanks, Jim, I did kinda/sorta figure that out after I posted the question.  I wasn't importing the script/using .psd1 as I needed to slightly modify S.DS.P.psm1 to work in my environment.  So, I copied S.DS.P.psm1 to a regular .sp1 file and I'm using that copy.  I had to add "Add-Type -AssemblyName System.DirectoryService.Protocols" to get the script to run.
      
      However, I still cannot connect to our LDAP server.  I should mention that our LDAP server is NOT Active Directory.  It is a NetIQ implementation on a Linux box (I think--I'm no LDAP or NetIQ guru but we use a NetIQ web interface to manage it, so I'm guessing it is a NetIQ LDAP implementation).
      
      The tweaks to the script I made are minor.  Around lines 168-172, I removed $domain from the $cred NetworkCredential variable because our LDAP server is not part of a domain.  I also had to add a $auth="Basic" variable and add it to the end of $LdapConnection because I kept getting an error that the LDAP server did not use the default authentication type (I get an error, “the authentication type is not supported”).
      
      With these changes, the script does run, but never logs onto the LDAP server.  I keep getting an error "The supplied credential is invalid."  I know the credential is valid because it is used in many Python scripts on a Linux server to automate some tasks.
      
      Not sure if this is important, but those Python scripts connect to the server by "ldaps://server1.univ.edu:637" (I've changed the actual server name for security, of course).  This PS script doesn't use the ldaps:// prefix, so I couldn't for the life of me connect on port 637.  I tried with  -port option and also using –UseSLL but I always got an error that the server was unavailable.  When I switched to the default port 389, I would then get past the server unavailable error but I’m now stuck at the invalid credential error mentioned before.
      
      (continued in next post...)
    • (continuation)
      
      My best guess at this point is that the .NET classes involved only work with Microsoft’s implementation of LDAD (that is, AD) and not any other.  For example, the authentication options available with System.DirectoryServices.Protocols (Anonymous, Basic, Digest, Dpa, Kerberos, Msn, Negotiate, Ntlm and Sicily) seem to be completely different than the standard LDAP options I found from a web search (Anonymous, Simple, and SASL).  Maybe the two Anonymous are the same, but our server doesn’t support that.  I’m guessing Basic is similar to Simple but perhaps not good enough to actually logon?  And there is no SASL option with DirectoryServices.Protocols so I can’t even try that.
    • Hello,
      yes, when you want to load assembly yourself, the way to go is via Add-Type, or [System.Reflection.Assembly]::LoadWithPartialName()
      
      Regarding what you're trying to achieve:
      - if the SSL endpoint is open on the server side, then LdapConnection will connect, provided that it can verify server certificate. If cert cannot be verified (such as server name provided to the connection does not match to what's in SSL certificate), then you'll get ServerUnavailable error. Below works easily for me (connecting with implicit creds):
         $di=new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier("dc.dom.com",636)
          $conn=new-object System.DirectoryServices.Protocols.LdapConnection($di)
          $conn.SessionOptions.SecureSocketLayer=$true
          $conn.Bind()
      
      - SimpleBind authentication shall work as well. Below sample code:
          $di=new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier("dc.dom.com",636)
      $conn=new-object System.DirectoryServices.Protocols.LdapConnection($di)
      $conn.SessionOptions.SecureSocketLayer=$true
      $conn.AuthType="Basic"
      $cred=new-object System.Net.NetworkCredential("CN=User,CN=Users,DC=dom,DC=com","password")
      $conn.Credential=$cred
      $conn.Bind()
      
      - SASL auth over non-ssl port shall work as well (as far as I remember, server must support LDAPv3 for this to work), see sample code below:
      
      $di=new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier("dc.dom.com",389)
      $conn=new-object System.DirectoryServices.Protocols.LdapConnection($di)
      
      $conn.AuthType="Basic"
      $cred=new-object System.Net.NetworkCredential("CN=User,CN=Users,DC=dom,DC=com","password")
      $conn.Credential=$cred
      $conn.SessionOptions.ProtocolVersion=3
      $conn.SessionOptions.StartTransportLayerSecurity($null)
      $conn.Bind()
      
      Overall, I guess that sample with SimpleBind auth shall work for you, as LDAP servers are generally expected to support this method. if you're able to publish LDAP enpoint, I will test
      
      Regards,
      Jiri
    • Jim, I tried all three of your snippets and still no go.  The first one ,–SSL, still gives me a “LDAP server is unavailable error.”  Ditto for the –SimpleBind one.  The –SASL one give me an error, “Exception calling "StartTransportLayerSecurity" with "1" argument(s): "A local error occurred."
      
      I also tested them from a different host, instead of my workstation, on a Windows 2012 server.  I get the same results there, too, except I don’t get the ‘local error’ for the –SASL snippet but it does give the “LDAP server is unavailable error.”  
      
      But now for an interesting twist…I decided to try this out on a completely different LDAP server (again, NOT an Active Directory server).    I tried your snippets on server2.system.univ.edu with different admin credintials, cn=myadmin,o=univ.  I couldn’t connect using SSL on this sever either, but if I removed the SSL info in the snippets, and did a default connection to port 389, they worked!  I could connect and bind to this server.
      
      Which leads me to wonder…was the “supplied credential is invalid” error I was getting on the first server actually accurate—that the ID and/or password I was using with it was bad?  But how could it be bad when it is running changes from a Python script on a Linux machine?  I’ll have to check with that server’s administrator to figure it out.
      
      In the meantime, I went back to test the S.DS.P script, but this time connecting to the second server mentioned above.  The script ran further than before, but still errored out.  I get the error “The server does not support the control. The control is critical” around line 250 ($rsp=$LdapConnection.SendRequest($rq, (new-object System.Timespan(0,0,$TimeoutSeconds))) –as [System.DirectoryServices.Protocols.SearchResponse];)
      
      I have no idea what that means.
      
    • Hello,
      the error you report at the end simply says that your LDAP server does not support timeout on the requests. Just replace the line
      $rsp = $LdapConnection.SendRequest($rq, (new-object System.Timespan(0,0,$TimeoutSeconds))) -as [System.DirectoryServices.Protocols.SearchResponse];
      with line
      $rsp = $LdapConnection.SendRequest($rq) -as [System.DirectoryServices.Protocols.SearchResponse];
      any you should get over this
      
      Hope this helps,
      Jiri    
    • Excellent! Thanks for the help.
    • Just a few followups as I've also used this script to connect to a NetIQ LDAP catalog.
      I used a separate function to create the connection and then passed that to this function. That allowed me to better tweak the connection parameters to suit non microsoft LDAP directories. I connected via StartTLS (as ldaps:// is deprecated in favor of Start TLS).
      
      Also I had to disable the ranged property retrieval for attribute logic entirely as it simply didn't work for me against NetIQ's eDirectory becuase they don't support this draft/proposed LDAP extension. The code should check for object identifier 1.2.840.113556.1.4.802 in the supportedControls operational attribute on the rootDSE. Clients must not use the range option unless this object identifier is present. 
    • @Alex: We now support non-ranged attribute retrieval, via parameter RangeSize. I decided to leave this on called to decide whether or not to use ranged attribute retrieval ratheer than automatically check RootDSE.
      
      Thanks for input!
      Jiri
  • PropertiesToLoad is not returning any properties
    4 Posts | Last post August 31, 2018
    • Hi,
      
      When I use Find-LdapObject against a NetIQ LDAP (eDirectory) server, properties specified using the PropertiesToLoad option are not returned.  There is no error, just empty columns for the requested properties.  Find-LdapObject does return the distinguishedName, so the search itself is working.  When I run it against AD it does return the properties; it seems this may only be an issue for non AD LDAP servers.
      
      Has anyone else experienced this, and any ideas on what the issue may be?
      
      Just to add, I'm currently using v1.7.5 of S.DS.P module.
      
      Thanks,
      
      Matt
    • I had the same issue as you but using IBM Directory Services instead of NetIQ. After some troubleshooting I was able to get expected results by modifying line 290 of S.DS.P.psm1:
      
            From this: $rng = "$($attrName.ToLower());range=$start`-$($start+$rangeSize-1)"
            To this: $rng = $attrName.ToLower()
      
      Seems to screw with results when not using ActiveDirectory as the target LDAP system. I know you have probably moved on by now, but figured someone else might come across the same issue.
    • Hi Matt, Steve,
      might be that servers you use may not support ranged attribute retrieval. I will look at this and may add support to load of attribute values without ranged retrieval.
      
      PS I do not monitor this Q/A very often, so apologies for late answer. If possible, please use https://github.com/jformacek/S.DS.P/issues to ask questions/log problems
      
      Thank you for testing with non-MS LDAP server,
      Jiri
    • Just update module to support loading of attributed without ranged retrieval. Give it a try please
      
      Regards,
      Jiri
  • The server does not support the control. The control is critical
    2 Posts | Last post August 31, 2018
    • Hi,
      
      I have an open ldap server, I have test using anonymous and basice authentication but always I got the error The server does not support the control. The control is critical, when I search.
      
      The connection is:
      $di=new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier("server.domain.com",389)
      $conn=new-object System.DirectoryServices.Protocols.LdapConnection($di)
      $conn.SessionOptions.SecureSocketLayer=$false
      $conn.AuthType="Anonymous"
      $conn.Bind()
      
      The error using find-ldapsearch is:
      Exception calling "SendRequest" with "1" argument(s): "The server does not
      critical."
      At C:\apl\S.DS.P.psm1:211 char:17
      + ...             $rsp = $LdapConnection.SendRequest($rq, $timeout) -as [System.Direc
    • Hi Felix,
      might be that your LDAP server does not support paging. Did you try with parameter -PageSize:0 ? Setting PageSize to 0 causes not to send PageResultRequestControl to server.
      
      PS I do not monitor this Q/A very often, so apologies for late answer. If possible, please use https://github.com/jformacek/S.DS.P/issues to ask questions/log problems
      
      Thank you,
      Jiri
  • How to successfully pass the parameters to the cmdlet?
    1 Posts | Last post June 15, 2018
    • Hi Jiri,
      
      The LDAP script is really helpful and time-saver. While passing values for SerachBase, I am getting below exception. Can you have a look into it that what am I missing once you get a moment?
      
      --
      Exception calling "SendRequest" with "2" argument(s): "The distinguished name contains
      invalid syntax."
      At C:\Program Files\WindowsPowerShell\Modules\S.DS.P\S.DS.P.psm1:248 char:17
      + ...             $rsp = $LdapConnection.SendRequest($rq, $Timeout) -as [Sy ...
      +                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
          + FullyQualifiedErrorId : DirectoryOperationException
      --
      
      Thanks,
      DSal
  • Proposed improvements
    4 Posts | Last post February 08, 2017
    • From a design perspective, I think it is far better to not intermingle the "bind/connection" with the "search" functionality. Why not split this out to a separate function within the same module?
      
      That would also allow future extension to create/update/delete objectsion using same connection object via additional functions.
    • Hi Oz,
      this is consolidated reply for your recent posts to different threads below.
      
      originally, the sample was meant to showcase S.DS.S to support people to switch from ADSI/DirectoryEntry objects. I noticed it gained some popularity - which is great as it shows the original purpose was reached.
      It's tested mostly against AD/AD LDS - while I'm in consulting business, I rarely meet other directories to the extent that would allow to test the script against.
      
      I also have code samples written that allow adding new objects, and editing and deleting existing - however, did not have enought time to test them to meet expected quality bar.
      
      Maybe you would be interested in helping with development? I have a workspace on Github, so let's move the source code there, for better cooperation on adding new features by people who have better access to various brands of directory servers.
      This site can still host recent, tested versions for wide public
      
      Thoughts?
      
      Jiri
    • Jiri,
      
      I've only written very basic code for adding new objects as that was required by my current project.
      
      Happy to contribute my changes back to a central workspace, and can readily test against some other LDAP implementations (primarily NetIQ eDirectory).
      
      Alex
    • Hi Alex,
      I will create a github repo with sources this week and let you know.
      Also, I have some code ready that allows addin/modifying LDAP objects - based on passing dictionary with props. Will create skeleton implementation for you to work on
      Would be great to have someone to test on other LDAP server platforms
      
      Thanks,
      Jiri
1 - 10 of 20 Items