How to Program to NDS eDirectory on NetWare Using Perl
Articles and Tips: article
Software Consultant
Net Technology Services Group
sguruprasad@novell.com
01 Feb 2001
This AppNote addresses how to write Perl scripts to perform tasks with NDS eDirectory.
- Introduction
- Essentials
- Scenarios
- Schema Extensions
- Executing Perl Scripts on NetWare
- Documentation and Binaries
- Conclusion
Introduction
The Directory is an essential part of growing the Internet community. Programming to the Directory from Perl combines the power of eDirectory and the flexibility of Perl in developing good applications. This article addresses how to write Perl scripts to perform tasks (login and logout, creation and deletion of objects, retrieval and modification of attribute values, searching, extending schema etc.) with the Novell Directory Services (NDS) eDirectory, henceforth referred to as the Directory.Perl uses Universal Component System (UCS) and NWDir UCX (Universal Component eXtension) Component to talk to the Directory. For information on writing Perl scripts to access Novell services using UCS see Accessing Novell Services from Perl on NetWare (http://support.novell.com/techcenter/articles/ana20001007.html) published in the October 2000 issue of AppNotes.
Essentials
Any Perl script that accesses an UCS component has to include the Perl Extension for UCS in the script. This is the first and most important step.
use UCSExt;
The above statement pulls in the definitions and declarations from the above mentioned module and loads the necessary extension file into the memory if it is not yet loaded. Next it is important to instantiate the component by calling the new method of the UCSExt package.
$Dir = UCSExt->new(>UCX:NWDir>) or die >Failed to instantiate the NWDir UCX component.\n>;> print ("Successfully instantiated the NWDir UCX component.\n");
In the above statement, NWDir is the Directory UCX Component. The new method instantiates the Directory object and stores it as a Perl object for later use.
Scenarios
In this section we will look at how different tasks are performed on the Directory from Perl.
Log in and Log out
The first and foremost activity to be performed, before doing anything else on the Directory, is to login. Also once all the operations are completed, if you are logged in, it is necessary to log out of the Directory. This will clear the connection and does the necessary cleaning up of the resources. Except for operations like browsing the Directory and searching to a limited extent, logging in to the Directory is mandatory.
use UCSExt; $Dir = UCSExt->new(>UCX:NWDir>) or die >Failed to instantiate the NWDir UCX> component.\n";" print ("Successfully instantiated the NWDir UCX component.\n");" $Dir->Login(>username>,>password>) or die >Failed to login. Verify the username> and password.\n";" print ("Successfully logged into the Directory.\n");" # TO DO# $Dir->Logout;> print ("Successfully logged out of the Directory.\n");"
In the above code, $Dir is the Directory object instantiated by calling the new method of UCSExt package. The two parameters username and password represent a valid user name in the Directory and its password. If the user object is not present in the current context, the Fullname property has to be set to point to the correct context. The Logout method of the Directory object is called to log out of the Directory. This code can be used as a template with the required functionality written in the "TO DO" portion.
Add an Entry
Adding a new entry to the Directory requires proper rights. Ensure that the user for whom the login method is called has the necessary rights. It is not just for this operation, proper rights are required to perform any operation on the Directory. If an operation is attempted without sufficient rights, it will fail.
# Get the Entries object# $Entries = $Dir->{>Entries>};> # Shortname of the Object to be created# $Objectname = "new_user";" # Surname, Title # $Surname = "user_surname";" $Title = "Consultant";" $EMailID = "new_user\@somewhere.com";" # Call the AddElement method of the Entries object# # Creating an object of type User# $Entry = $Entries->AddElement($Objectname, "User") or die "Failed to add element: $Objectname.\n"; # Mandatory property(Surname) for User object, has to be set# $Entry->SetFieldValue(>Surname>, $Surname) or die >Failed to set Surname:> $Surname.\n";" # Set any other optional properties like Title # $Entry->SetFieldValue("Title", $Title) or die "Failed to set Title: $Title.\n"; $Entry->SetFieldValue("Internet EMail Address", $EMailID) or die "Failed to set Internet Email Address: $EMailID.\n"; # Call Update method to commit the changes# if ($Entry->Update()) { print ("Successfully created the new entry: $Objectname.\n");" } else { print ("Update failed, $Objectname, might be a duplicate entry.\n"); }
The above code snippet adds a new user object, sets the mandatory property Surname and optional additional properties. Then, it calls the Update method to commit the changes. To set more properties retrieve the desired object, set the field values, and then call the Update method. The object is added to the context pointed to by the Fullname property. To create the object in a different context, set the Fullname property to the desired context.
Look for an Entry
Once an entry is created in the Directory, it can be found in different ways. One way is to use the Item method of the Entries object to get the Entry object.
# Shortname of the Object to be found# $Objectname = "new_user"; # Get a collection of Entries from the current context# $Entries = $Dir->{"Entries"}; # Get the Entry object# $Entry = $Entries->Item($Objectname); if ($Entry) { print "Found!!!\nFullname: ", $Entry->{"FullName"}, "\n"; } else { print ("Failed to find the specified entry: $Objectname.\n") }
The above code tries to retrieve the entry from a collection of entries from the current context. To find an entry in a different context, the Fullname property has to be set accordingly.
Modify Attribute Values for an Entry
Once an entry is added to the Directory, its attributes can be modified. The operations that can be performed are modifying attribute values, adding new attribute values or removing attribute values. In the previous sample, Item method on the Entries object is used to get the Entry object. One other way of finding an entry in the Directory is by using the Fullname property with the FindEntry method.
# Concatenate the Entry to be found # $Fullname = $Dir->{"Fullname"}."\\new_user"; # Find the specified Entry object# $Entry = $Dir->FindEntry($Fullname) or die "Failed to find the entry: $Fullname.\n"; # Fill an array with the Postal address# @PostalAddress = ("new_user", "007", "24th street", "this_city", "this_state", "10001"); @OldPostalAddress = $Entry->GetFieldValue("Postal Address"); # If there is an address, overwrite with the new address# if (@OldPostalAddress) { $Entry->SetFieldValue ("Postal Address", \@PostalAddress, \@OldPostalAddress) or die "Failed to set Postal Address.\n"; } else { $Entry->SetFieldValue ("Postal Address", \@PostalAddress) or die "Failed to set Postal Address.\n"; } $OldEMail = $Entry->GetFieldValue("Internet EMail Address", false); $NewEMail = "this_user\@somewhere.com"; $Entry->SetFieldValue("Internet EMail Address", $NewEMail, $OldEMail) or die "Failed to set Internet Email Address: $NewEMail.\n"; # Call the Update method to commit the changes done to the Entry object# if($Entry->Update()) { print ("Successfully updated the properties.\n"); } else { print ("Failed to update the properties.\n"); }
This above code snippet modifies the property values of the User object. Property values are added for the two properties Postal Address and Internet Email Address.
The property Postal Address needs an array of strings. The parameter, containing the Postal Address, is passed by reference to the SetFieldValue method. If it is not passed by reference, then Perl treats all the input parameters as one array. The old postal address is passed into the SetFieldValue method; this will ensure that there will be only one address and not multiple addresses. The reason being, Postal Address property can hold multiple address values. If multiple addresses are required, then there is no need to pass the old postal address.
Similar to Postal Address property, the Internet Email Address property can also hold multiple values. To have a single value at a time, the old value is also passed to SetFieldValue method. If old value is not passed and new value is the same as the old value, Update will fail. But if the new value is different from the old value, then the property will hold both the values.
View User Objects
The previous samples showed the use of Item method and FindEntry method to find specific entry objects in the Directory. Another way of finding entries is the Filter object. The Filter object starts its search from the default context and continues based on the scope set in the filter object. The Fullname property can be set to search for objects from a different context.
# Get the Filter object# $Filter = $Dir->{"Filter"}; # Set the Scope of Search# $Filter->{"Scope"} = $Filter->{"SEARCH_SUBTREE"}; # Formulate the query# # Search for objects where the Object Class is of type User# $Filter->AddExpression($Filter->{"FTOK_EQ"},"Object Class","User"); # Terminate the filter expression# $Filter->AddExpression($Filter->{"FTOK_END"}); # Get a collection of Entry objects for the given filter# $Entries = $Dir->Search($Filter) or die Failed to get the collection of entry objects.\n"; # Get all of the searched entries# $Entries->Reset(); while($Entries->HasMoreElements()) { $Entry = $Entries->Next();> # Display the Fullname # print $Entry->{"Fullname"}, "\n"; # Get the Layout Description for this Entry # $Layout = $Entry->{"Layout"}; # Retrieve all the Fields present in this Layout # $Fields = $Layout->{"Fields"}; $Fields->Reset(); # Get the Field value for each of the Fieldnames# while($Fields->HasMoreElements()) { $Field = $Fields->Next(); $Fieldname = $Field->{"Name"}; $Value = $Entry->GetFieldValue($Fieldname); # Display only if the property has some value# # Display the property name and its value# if ($Value) { print " ", $Fieldname, "=", $Value, "\n"; } } # Pause after all the properties for each User is displayed# print ("Press Enter Key to continue..."); <stdin>; } print "Done.\n";
The above code shows how to perform search on the Directory. The sample code snippet searches for all User objects within the specified scope. Filter object is used to set up the scope and the filter expression. The scope is set to SEARCH_SUBTREE, which initiates a search under the set context and its subordinate contexts. The filter expression is set to search for all User objects. The above sample also lists the values of all the fields that are set. The filter object can be used to formulate more complex queries and retrieve the required information.
View Current Context
The current context can be located by using the Fullname property of the Directory object. This is a read/write property, which can be set to any other valid context and Directory operations can be performed in that context.
# Print the current context# print "Current context: ", $Dir->{"Fullname"}, ".\n"; # Set the Fullname property to a different context# $Dir->{"Fullname"} = $Dir->{"Fullname"} . "\\new_context"; # Display the new context# print "New context: ", $Dir->{"Fullname"}, ".\n";
The above code displays the current context, changes the context to the new context and displays the same.
Schema Extensions
Until now we have seen operations like adding new entries to the Directory, searching, retrieving, and modifying attribute values from Perl. Now let's take a look at more complex operations on the Schema.
View Existing Layouts
The Directory will typically have a set of predefined layouts. Also new layouts based on the existing layouts might have been created.
# Get the collection of all the Layout Descriptions# $LayoutDescs = $Dir->{"Layouts"}; # Initialize the collection to enumerate all the Layout Description objects# $LayoutDescs->Reset(); while ($LayoutDescs->HasMoreElements()) { $LayoutDesc = $LayoutDescs->Next(); # Display some of properties of each Layout# print ($LayoutDesc->{"Name"}, " ", $LayoutDesc->{"BasedOn"}, " ", $LayoutDesc->{"Removable"},"\n"); } print "Done.\n";"
The preceding code retrieves all the existing layouts in the Directory. It displays the name of the layout, from what base layout this layout is derived from and whether it is removable or not.
Create a New Layout
Having seen all of the existing layouts, sometimes it might be required to add a new layout. A new layout can be added to the Directory by calling the Add method on the LayoutDescriptions object.
# Get the Layout Descriptions object# $LayoutDescs = $Dir->{"Layouts"}; # Mandatory Field(s) in the new Layout# @MandatoryFields = ("CN"); # Under which existing Layout(s) should the objects be created# @ContainmentLayouts = ("Organization", "Organizational Unit"); # Field(s) which can be used for naming objects of this Layout# @NamingFields = ("CN"); # Name of the new Layout# $LayoutName = "Test Layout"; # Layout from which the new Layout inherits the Fields# $BaseLayout = "Top"; $LayoutDesc = $LayoutDescs->Add($LayoutName, $BaseLayout, \@MandatoryFields,\@ContainmentLayouts,\@NamingFields); if ($LayoutDesc) { print ("Successfully created the new layout: $LayoutName. Finding the new layout...\n"); # Find the newly created Layout# $LayoutDescs = $Dir->{"Layouts"}; $LayoutDesc = $LayoutDescs->Item($LayoutName) or die "Failed to get the description for $LayoutName.\n"; print ("Found!!!\n"); print (" Name of new layout: ", $LayoutDesc-"{"Name"}, "\n"); print (" BasedOn: ", $LayoutDesc-"{"BasedOn"}, "\n"); } else { print ("Failed to created the new layout: $LayoutName. Might be a duplicate.\n"); }
The above code will add a new layout to the Directory. The array parameters for the Add method are passed by reference. If array parameters are not passed by reference, then all the parameters will be sent in as a single array. The MandatoryFields array indicates the fields that are mandatory when creating an object based on this new layout. The ContainmentLayout array indicates under which layouts should the objects based on this new layout be created. The comments in the above sample give descriptions for the other parameters of the Add method.
View Existing Fields
All the fields in the Directory will have a name and a type. The type can be an Integer, Boolean or can be a complex type like Path, ACL etc. Additionally the field can be a single valued or multi-valued and removable or non-removable from the Schema. This information is available in the properties of the FieldType object.
# Get the collection of all the Field types (NDS Syntax)# $FieldTypes = $Dir->{"FieldTypes"}; if ($FieldTypes) { # Initialize the collection to enumerate all the Field Description objects# $FieldTypes->Reset(); while ($FieldTypes->HasMoreElements()) { $FieldType = $FieldTypes->Next(); # For each Field Description, display the Name, Syntax type, NDS name of# the syntax and Removable from the Schema or not print ($FieldType->{"Name"}, " ", $FieldType->{"SyntaxType"}, " ", $FieldType->{"SyntaxName"}, " ", $FieldType->{"Removable"}, "\n"); } } else { print ("Could not get a collection of all the field types.\n"); }
The above code snippet retrieves all the existing fields from the Directory. It displays the field name, syntax name, syntax type and whether it is removable from the Schema or not.
Add a New Field to the Schema
The Directory has fields that cater to most of the needs, but sometimes it might so happen that a suitable field is not present in the Schema. In such a situation, extending the Schema to add the required field is the only alternative.
# Name of the new Field to be created# $FieldName = "newfield"; # Syntax type - SYN_INTEGER# $FieldType = 8; # Get the Field types object# $FieldTypes = $Dir->{"FieldTypes"}; # Add the new Field to the Directory# $FieldType = $FieldTypes->Add($FieldName, $FieldType); if ($FieldType) { print ("Successfully created the new filed: $FieldName. \n"); } else { print ("Failed to create the new filed: $FieldName. Might be a duplicate. \n"); }
The above code snippet adds a new field named newfield to the Schema. The FieldType parameter for the Add method specifies the type of the new field. It can be an Integer, Boolean or it can also be a complex type like Path, ACL etc. The NWDir UCX Component documentation gives a complete list of all the supported syntax types.
Once the field is created, to be used, it has to be associated with a layout. The layout can be an existing layout or it can be created as shown above.
# Layout into which the new Field is to be added# $LayoutName = "Test Layout"; # Name of the Field to be added to the Layout# $FieldName = "newfield"; # Get the collection of Layouts# $LayoutDescs = $Dir->{"Layouts"}; # Find the Layout for which the Field is to be added# $LayoutDesc = $LayoutDescs->Item($LayoutName) or die "Failed to get the Layout description for $LayoutName.\n"; print "Got layout: ", $LayoutDesc->{"Name"}, ", Base layout:", $LayoutDesc->{"BasedOn"},"\n"; # Get the collection of Fields for this Layout# $FieldDescriptions = $LayoutDesc->{"Fields"}; # Add the Field to the Layout for which this Field Descriptions object belongs to# $FieldDescription = $FieldDescriptions->Add($FieldName); if ($FieldDescription) { print ("Successfully added the field $FieldName to $LayoutName. \n"); } else { print ("Failed to add the field $FieldName to layout $LayoutName\n"); }
The above code snippet adds the field newfield to the layout Test Layout. It assumes that a layout by name Test Layout is present in the Directory and a field by name newfield also exists.
Delete an Object
There will always be times when objects from the Directory are to be removed.
# Shortname of the object to be removed# $ObjectName = "new_user"; # Get the Entries object# $Entries = $Dir->{"Entries"}; # Call the Remove method of the Entries object, # $Return = $Entries->Remove($ObjectName); if ($Return) { print ("Successfully removed the object: $ObjectName.\n"); } else { print ("Failed to remove the object: $ObjectName. Might not exist.\n"); }
The above code removes an object, given its Shortname, from the Directory.
Remove Layouts and Fields
All the layouts for which the Removable property in the LayoutDescription object is True can be removed. Similarly all the fields for which the Removable property in the FieldType object is True can be deleted from the Directory. There is an exception to the previous statement. Fields once added to non-removable layouts like User, Group etc. can't be deleted from the Directory. The sequence for removing both the layout and field is to first remove the layout and then delete the field.
# Name of the Layout to be removed# $LayoutName = "Test Layout"; # Get the Layout Descriptions object# $LayoutDescs = $Dir->{"Layouts"}; # Remove the Layout# $Return = $LayoutDescs->Remove($LayoutName); if ($Return) { print ("Successfully removed the layout: $LayoutName.\n"); } else { print ("Failed to remove layout: $LayoutName. Might not exist.\n"); } # Name of the Field to be removed# $FieldName = "newfield"; # Get the FieldTypes object# $FieldTypes = $Dir->{'FieldTypes"}; # Remove the Field from the directory# $Return = $FieldTypes->Remove ($FieldName); if ($Return) { print ("Successfully removed the field: $FieldName.\n"); } else { print ("Failed to remove the field: $FieldName.\n"); }
The above code removes a layout named Test Layout from the Directory. Once the layout is removed, any field associated with it can also be removed; provided the field is not associated with any of the non-removable layouts. The field newfield is also deleted from the Directory.
Each of the snippets shown above has the code for a particular functionality. To make each one of these into a full-fledged program, add the code into the template provided in the "Log in and Log out" section in the beginning of this article. One more point to keep in mind is that the logged in user must have sufficient rights to perform different operations.
Executing Perl Scripts on NetWare
This section is not in any way connected to the Directory, but discusses the various ways in which the Perl scripts can be invoked on a NetWare server. The alternatives available are:
As a CGI Script
Perl Script Embedded in an ASP File
From the Server Console
As a CGI Script
Perl scripts can be executed from a Browser. For this the script must be under perlroot\samples directory inside the document root (novonyx\suitespot\docs) directory of the server. Take a look at the samples, copied into this directory during installation, for better understanding.
Perl Script Embedded in an ASP File
In NetWare, Perl scripts can be embedded in an Active Server Page (ASP) file. The .asp file has to be under the document root (novonyx\suitespot\docs) directory.
From the Server Console
Any Perl script stored in the sys:\perl\scripts directory can be executed from the server console. Scripts stored in other directories and which are listed in the PATH environment variable can be invoked by using the -S switch. Additionally scripts can also be executed by setting the PERL_ROOT environment variable to a directory that contains the scripts.
Documentation and Binaries
The latest documentation and binaries for Perl are available in the Novell Developer Kit (NDK) at http://developer.novell.com/ndk/perl5.htm. Additional related links are:
Conclusion
This article discussed how to program to the Directory on NetWare using Perl. There are different ways in which the scripts can be executed, as shown above in the "Executing Perl scripts on NetWare" section, which gives flexibility to the user. These advantages combined with the power of Perl will be helpful in developing very powerful applications to the Directory on NetWare.
* Originally published in Novell AppNotes
Disclaimer
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.