Monday, May 6, 2013

Deleting Entries in Non-Microsoft LDAP Directories in .Net and PowerShell

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

 A previous post of mine that covered LDAP searches in non-Microsoft Active Directory LDAP directories (ex. OpenLDAP, Oracle Internet Directory, Novell eDirectory, etc.) gained a fair amount of popularity and I received requests to follow up on it to provide instructions on performing other LDAP functions using the .Net Framework and PowerShell.

Continuing from my last post, I went ahead and set up an OpenLDAP server for demonstration purposes utilizing the core, cosine, and inetorgperson schemas. I have prepopulated it with the following objects:

dn: dc=mikesblog,dc=lan
objectclass: dcObject
objectclass: organization
o: Mikes Technology Blog
dc: mikesblog

dn: cn=Manager,dc=mikesblog,dc=lan
objectclass: organizationalRole
cn: Manager

dn: ou=testou1,dc=mikesblog,dc=lan
objectclass: organizationalUnit
objectclass: top
ou: testou1
description: My first test OU

dn: cn=mike,ou=testou1,dc=mikesblog,dc=lan
objectClass: person
objectclass: inetorgperson
givenName: Mike
sn: Burr
uid: mike

dn: ou=testou2,dc=mikesblog,dc=lan
objectclass: organizationalUnit
objectclass: top
ou: testou2
description: My second test OU

In this example, we will delete the testuser user in testou2.

To accomplish this, we will use the .Net framework classes in the System.DirectoryServices.Protocols namespace.  The LdapConnection class gives our application connectivity to the LDAP server where we can provide an DeleteRequest object and receive an DeleteReply. In the DeleteRequest, we specify via the Modifications property what attribute additions, modifications, and deletions need to occur.

The sample code below is designed to demonstrate how to modify attributes for an LDAP entry, in practice this might be designed to use the command pattern instead of the hardcoded transaction script below.

The first code example shows some discussion of the delete subtree server control. This is typically used to delete an object and all objects under it in a directory tree. It is not supported on all platforms. Microsoft and IBM publicize the ability to use this server control with their LDAP implementations, but others, such as OpenLDAP (as of 2.4) do not support it.

Let's get to it... The sample C#.Net code:



// Connects to myopenldap.mikesblog.lan
// on the standard port
LdapConnection c = new LdapConnection("myopenldap.mikesblog.lan:389");

//Set session options
c.SessionOptions.SecureSocketLayer = false;
c.SessionOptions.ProtocolVersion = 3;

// 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));

// We are going to delete the user below (LDIF Below)
/*

dn: cn=testuser,ou=testou2,dc=mikesblog,dc=lan
objectclass: person
objectclass: inetorgperson
uid: testuser
givenName: Test
sn: User
 
*/

DeleteRequest r = new DeleteRequest();

//What are we deleting?
r.DistinguishedName = "uid=testuser,ou=testou2,dc=mikesblog,dc=lan";

// Use the delete subtree server control to
// remove any objects under the targeted object
// Not all LDAP implementations support this, so this may
// throw an exception:
/*
System.DirectoryServices.Protocols.DirectoryOperationException: A protocol error occurred.
at System.DirectoryServices.Protocols.LdapConnection.ConstructResponse(Int32 messageId, LdapOperation operation, ResultAll resultType, TimeSpan requestTimeOut, Boolean exceptionOnTimeOut)
at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request, TimeSpan requestTimeout)
at System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request)

 * To figure out what hapened, catch the DirectoryOperationException
 * and look at Response.ErrorMessage:
try {
...
}
catch (DirectoryOperationException e)
{
    Console.WriteLine(e.Response.ErrorMessage);
}

 * Since OpenLDAP doesn't support this control, it returns:
 * treeDelete control value not absent
 *
 * Depending ont the server's configuration, it'll also show up in the OpenLDAP logs

May  6 21:46:06 myopenldap slapd[61167]: conn=1032 op=1 RESULT tag=107 err=2 text=treeDelete control value not absent
May  6 21:46:06 myopenldap slapd[61167]: conn=1032 op=1 do_delete: get_ctrls failed
May  6 21:47:21 myopenldap slapd[61167]: conn=1032 fd=18 closed (connection lost)


 */

//Uncomment the line below for subtree delete (if your server supports it)

//r.Controls.Add(new DirectoryControl("1.2.840.113556.1.4.805", null, true, true));


//Actually process the request through the server
DeleteResponse re = (DeleteResponse)c.SendRequest(r);


if (re.ResultCode != ResultCode.Success)
{
    Console.WriteLine("Failed!");
    Console.WriteLine("ResultCode: {0}", re.ResultCode);
    Console.WriteLine("Message: {0}", re.ErrorMessage);

}


Console.Read();


Not to disappoint, below is the PowerShell port of the above code.


#Mike Burr
#Script Connects to and Deletes OpenLDAP directory entry

#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:389"
          
#Set session options
$c.SessionOptions.SecureSocketLayer = $false
$c.SessionOptions.ProtocolVersion = 3

# 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)

# We are going to Delete this object (LDIF Below)
#

# dn: cn=testuser,ou=testou2,dc=mikesblog,dc=lan
# objectclass: person
# objectclass: inetorgperson
# givenName: Test
# sn: User
# uid: testuser

$r = (new-object "System.DirectoryServices.Protocols.DeleteRequest")
$r.DistinguishedName = "cn=testuser,ou=testou2,dc=mikesblog,dc=lan"

#See the code example above for discussion on the delete subtree control
# $r.Controls.Add((New-Object "System.DirectoryServices.Protocols.DirectoryControl" -ArgumentList "1.2.840.113556.1.4.805",$null,$true,$true ))

#Actually process the request through the server
$re = $c.SendRequest($r)

if ($re.ResultCode -ne [System.directoryServices.Protocols.ResultCode]::Success)
{
    write-host "Failed!"
    write-host ("ResultCode: " + $re.ResultCode)
    write-host ("Message: " + $re.ErrorMessage)
}











 

Updating Attributes in Non-Microsoft LDAP Directories in .Net and PowerShell

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

 A previous post of mine that covered LDAP searches in non-Microsoft Active Directory LDAP directories (ex. OpenLDAP, Oracle Internet Directory, Novell eDirectory, etc.) gained a fair amount of popularity and I received requests to follow up on it to provide instructions on performing other LDAP functions using the .Net Framework and PowerShell.

Continuing from my last post, I went ahead and set up an OpenLDAP server for demonstration purposes utilizing the core, cosine, and inetorgperson schemas. I have prepopulated it with the following objects:

dn: dc=mikesblog,dc=lan
objectclass: dcObject
objectclass: organization
o: Mikes Technology Blog
dc: mikesblog

dn: cn=Manager,dc=mikesblog,dc=lan
objectclass: organizationalRole
cn: Manager

dn: ou=testou1,dc=mikesblog,dc=lan
objectclass: organizationalUnit
objectclass: top
ou: testou1
description: My first test OU

dn: cn=mike,ou=testou1,dc=mikesblog,dc=lan
objectClass: person
objectclass: inetorgperson
givenName: Mike
sn: Burr
uid: mike

dn: ou=testou2,dc=mikesblog,dc=lan
objectclass: organizationalUnit
objectclass: top
ou: testou2
description: My second test OU

In this example, we will modify the testuser user in testou1 to be contained in a new container (ou=testou2,dc=mikesblog,dc=lan). Optionally, we can also rename the user (I show the field below, but don't actually rename the user). 

To accomplish this, we will use the .Net framework classes in the System.DirectoryServices.Protocols namespace.  The LdapConnection class gives our application connectivity to the LDAP server where we can provide an ModifyRequest object and receive an ModifyReply. In the ModifyRequest, we specify via the Modifications property what attribute additions, modifcations, and deletions need to occur.

The sample code below is designed to demonstrate how to modify attributes for an LDAP entry, in practice this might be designed to use the command pattern, content enricher pattern, and decorator pattern instead of the hardcoded transaction script below.

Let's get to it... The sample C#.Net code:



// Connects to myopenldap.mikesblog.lan
// on the standard port
LdapConnection c = new LdapConnection("myopenldap.mikesblog.lan:389");

//Set session options
c.SessionOptions.SecureSocketLayer = false;
c.SessionOptions.ProtocolVersion = 3;

// 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));

// We are going to modify this object (LDIF Below)
/*

dn: cn=testuser,ou=testou2,dc=mikesblog,dc=lan
objectclass: person
objectclass: inetorgperson
givenName: Test
sn: User
uid: testuser

*/

ModifyRequest r = new ModifyRequest();

//What are we changing?
r.DistinguishedName = "cn=testuser,ou=testou2,dc=mikesblog,dc=lan";

//First part of the request, delete the UID attribute for testuser
r.Modifications.Add(
    new DirectoryAttributeModification() {
        Name = "uid",
        Operation = DirectoryAttributeOperation.Delete
    }
);

//Second part of the request, add the UID attribute for testuser
DirectoryAttributeModification a = new DirectoryAttributeModification();
a.Name = "uid";
a.Operation = DirectoryAttributeOperation.Add;
//add values of the attribute
a.Add("testuseroops");

r.Modifications.Add(a);

//Third part of the request, replace the UID attribute for testuser
DirectoryAttributeModification m = new DirectoryAttributeModification();
m.Name = "uid";
m.Operation = DirectoryAttributeOperation.Replace;
//add value(s) of the attribute
m.Add("testuser");

r.Modifications.Add(m);


//Actually process the request through the server
ModifyResponse re = (ModifyResponse)c.SendRequest(r);

if (re.ResultCode != ResultCode.Success)
{
    Console.WriteLine("Failed!");
    Console.WriteLine("ResultCode: {0}", re.ResultCode);
    Console.WriteLine("Message: {0}", re.ErrorMessage);

}

Console.Read();


Not to disappoint, below is the PowerShell port of the above code.


#Mike Burr
#Script Connects to and Modifies Objects in OpenLDAP directory

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


#Connects to myopenldap.mikesblog.lan on the standard port
$c = New-Object System.DirectoryServices.Protocols.LdapConnection `
     "myopenldap.mikesblog.lan:389"
          
#Set session options
$c.SessionOptions.SecureSocketLayer = $false;
$c.SessionOptions.ProtocolVersion = 3

# 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);

# We are going to modify this object (LDIF Below)
#

# dn: cn=testuser,ou=testou2,dc=mikesblog,dc=lan
# objectclass: person
# objectclass: inetorgperson
# givenName: Test
# sn: User
# uid: testuser

$r = (new-object "System.DirectoryServices.Protocols.ModifyRequest")

$r.DistinguishedName = "cn=testuser,ou=testou2,dc=mikesblog,dc=lan";

#First part of the request, delete the UID attribute for testuser
$d = New-Object "System.DirectoryServices.Protocols.DirectoryAttributeModification"
$d.Name = "uid"
$d.Operation = [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Delete

$r.Modifications.Add($d)

#Second part of the request, add the UID attribute for testuser
$a = New-Object "System.DirectoryServices.Protocols.DirectoryAttributeModification"
$a.Name = "uid"
$a.Operation = [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Add
#add values of the attribute
$a.Add("testuseroops")

$r.Modifications.Add($a)

#Third part of the request, replace the UID attribute for testuser
$m = New-Object "System.DirectoryServices.Protocols.DirectoryAttributeModification"
$m.Name = "uid"
$m.Operation = [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Replace
#add value(s) of the attribute
$m.Add("testuser")

$r.Modifications.Add($m) 
#Actually process the request through the server
$re = $c.SendRequest($r);

if ($re.ResultCode -ne [System.directoryServices.Protocols.ResultCode]::Success)
{
    write-host "Failed!"
    write-host ("ResultCode: " + $re.ResultCode)
    write-host ("Message: " + $re.ErrorMessage)
}