Suppose the
notifier runs on an NT-based intranet, all the subscribers are listed
in the NT domain’s
Security Accounts Manager
(SAM) directory, and the FullName
slot of each
user’s directory entry includes an email address as well as a
first and last name. In this situation it makes sense to define the
group of subscribers using NT’sUser Manager and then let the
notifier consult the NT directory in order to enumerate subscribers
and fetch their email addresses.
As usual there’s more than one way to do it. You can manipulate the necessary Win32 APIs from a number of scripting languages, including Visual Basic, Python, and Perl. Hooking directly into the low-level Win32 APIs used to query the directory is, however, a wholly NT-centric approach. What if you’d also like to be able to work with a Novell NDS directory or an LDAP directory? Happily Microsoft has provided Active Directory Services Interface (ADSI), a component that works with all three of these directories—and potentially others as well. ADSI will be included as a standard component of Windows 2000. Until then, it’s (freely) available separately at http://www.microsoft.com/windows/server/Technical/directory/adsilinks.asp .
ADSI works on the ODBC model. It’s a driver manager that provides a generalized interface to data sources—in this case, directories—and that accepts pluggable drivers that adapt it to a range of specific data sources. As its name implies, ADSI’s eventual purpose is to connect applications to Microsoft’s own Active Directory. Since that product won’t be widely deployed anytime soon, ADSI’s immediate role is to encourage the development of directory-aware applications that will be compatible with Active Directory.
Example 11.4 shows Group::NTGroup , which supports the same interface as Group::SimpleGroup but which connects to an NT domain instead of a Perl-managed data structure. It’s written in Perl, which means our notifier can use it interchangeably with Group::SimpleGroup . But since ADSI runs as an OLE automation server, any OLE-aware scripting languages can use it. ADSI services are also available to those languages that can embed in ASP web pages—including VBScript, JavaScript, Python, and Perl.
Example 11-4. Group::NTGroup, a Simple NT-Oriented Group Directory
package Group::NTGroup; use strict; use Win32::OLE; sub new { my ($pkg,$domain,$group_name) = @_; my $self = { 'domain' => $domain, 'group' => Win32::OLE->GetObject("WinNT://$domain/$group_name,Group"), }; bless $self,$pkg; return $self; } sub members { my ($self) = @_; my @members = (); my $group = $self->{group}; my $m = $group->Members(); foreach my $member (in $m) { push (@members, $member->{Name}); } return @members; } sub isMember { my ($self,$member) = @_; return grep ( /^$member$/, $self->members ) ? 1 : 0; } sub setMember { my ($self,$member) = @_; if (! $self->existsUser($member) ) # if user doesn't exist { my $dn = "WinNT://$self->{domain}"; my $d = Win32::OLE->GetObject($dn); my $u = $d->Create("user",$member); # create user $u->SetInfo; } my $group = $self->{group}; # then add to group $group->add("WinNT://$self->{domain}/$member"); $group->SetInfo; } sub getMember { my ($self,$member) = @_; my $dn = "WinNT://$self->{domain}/$member"; $member = Win32::OLE->GetObject($dn); $member->GetInfo; my $member_hash = {}; for (1..$member->{PropertyCount}) { my $prop = $member->Next; $member_hash->{$prop->Name} = $member->Get($prop->Name); } return $member_hash; } sub delMember { my ($self,$member) = @_; my $group = $self->{group}; $group->remove("WinNT://$self->{domain}/$member"); $group->SetInfo; } sub setProperty { my ($self,$member,$prop_name,$prop_val) = @_; my $dn = "WinNT://$self->{domain}/$member"; $member = Win32::OLE->GetObject($dn); $member->GetInfo; $member->Put($prop_name,$prop_val); $member->SetInfo; } sub getProperty { my ($self,$member,$prop_name) = @_; my $dn = "WinNT://$self->{domain}/$member"; $member = Win32::OLE->GetObject($dn); $member->GetInfo; return $member->Get($prop_name); } sub existsUser { my ($self,$member) = @_; my $dn = "WinNT://$self->{domain}/$member,User"; my $u = Win32::OLE->GetObject($dn); return (defined $u) ? 1 : 0; } sub dumpGroup { my ($self) = @_; my $group_hash = {}; foreach my $member ($self->members) { $group_hash->{$member} = $self->getMember($member); } return $group_hash; } 1;
Group::NTGroup’s constructor shows how you
tap into ADSI. The parameter you pass to GetObject(
)
, called an AdsPath, has two
components. The leading part, which can be WinNT://, or NDS://, or LDAP://, selects the directory provider that
will satisfy all subsequent requests to the object created by this
call. The trailing part of the AdsPath names the location in a
directory tree to which the object will
refer.
In a Novell environment, you’d initialize the module with an AdsPath like NDS://RONIN/o=RoninHouse/cn=Subscribers. In an LDAP context, it would look similar: LDAP://RoninHouse.com/o=RoninHouse/cn=Subscribers.
An ADSI group object answers the Members( )
method call with a handle to an OLE collection of its members. In
VBScript you can enumerate that collection like this:
set group = GetObject("WinNT://UDELL/Subscribers") set members = group.Members for each user in members Wscript.echo user.name next
As you can see in Example 11.4, Win32 Perl supports
the same idiom. Perl wizards unfamiliar with Win32 Perl may not
recognize the construct (in $m)
, which isn’t
part of Perl’s standard repertoire. It’s a Win32
extension added to the ActiveState version of Win32 Perl so that
scripts can navigate OLE collections.
To operate on other parts of the directory, you call
GetObject( )
with different AdsPath strings. For
example, the getMember( )
method calls
GetObject("WinNT://DOMAIN/USERNAME")
to get a
handle to a user object so it can work with that user’s
directory entry.
We can’t get and set arbitrary properties here; the module only
works with the schema that governs the directory it’s talking
to. NDS, Active Directory, and others are extensible, but NT domains
aren’t. In this case the notifier can’t get and set an
email
attribute because there isn’t one. But
there are a couple of fields available for its use. As I mentioned,
one solution would be to always append an email address to the
FullName
field, for example: Jon Udell <[email protected]>
. Alternatively, you could tuck an
email address into the Description
slot of the NT
directory entry.