Re: question(s) re: win32::ole and active-directory interaction



aemaeth9@xxxxxxxxx wrote:
hello,

i'm curious, could anyone direct me to a half-decent article regarding
the Win32::OLE module, and using it to interact with active directory?
i've seen very little on it. the only articles i've come across that
discuss Win23::OLE where tips/tricks and tutorials on interacting with
miscellaneous objects in windows (e.g. opening a URL in IE). I've seen
a very small amount of articles, none of which where of any quality,
and they mentioned using LDAP as a bridge to AD. is this true? if not,
is there a better way? - any help would be much appreciated.


There are at least 2 modules on CPAN dealing with Active Directory or LDAP.

Win32::AD::User
Net::LDAP

I have attached a module I wrote to simplify ADSI queries. You should be able to get the basics of ADSI over Win32::OLE by looking at the code.
But the main information sources are the ADSI-API docs from M$.

Thomas

--
$/=$,,$_=<DATA>,s,(.*),$1,see;__END__
s,^(.*\043),,mg,@_=map{[split'']}split;{#>J~.>_an~>>e~......>r~
$_=$_[$%][$"];y,<~>^,-++-,?{$/=--$|?'"':#..u.t.^.o.P.r.>ha~.e..
'%',s,(.),\$$/$1=1,,$;=$_}:/\w/?{y,_, ,,#..>s^~ht<._..._..c....
print}:y,.,,||last,,,,,,$_=$;;eval,redo}#.....>.e.r^.>l^..>k^.-
package Local::ADSI;

use strict;
use warnings;

our $VERSION = "1.01";
our @ISA = qw(Exporter);

our @EXPORT = qw(
ADSIGetGroupMembersRecursive
ADSIQuery
ADSISetParam
$ADSILastErr
);

use Win32::OLE qw/in/;
Win32::OLE->Option(Warn => 1);

use Data::Dumper;
$Data::Dumper::Indent = 1;

our $ADSILastErr = '';

=head1 NAME

Local::ADSI - ADSI utility functions

=head1 VERSION

This document refers to version 1.01 of Local::ADSI,
released 04/2006

=head1 SYNOPSIS

use Local::ADSI;
use Data::Dumper;

my $all_users = ADSIQuery(
'MY.DOMAIN.NAME',
{qw/objectClass user/},
[qw/ADsPath Name displayName/],
1,
) or die $ADSILastErr;

print Dumper($all_users);


=head1 DESCRIPTION

Local::ADSI defines a few utility funtions that should make
common tasks easier to accomplish.

All Funtions return undef on error and set $ADSILastErr.

=head2 ADSIQuery($domain_ou, \%filter, \@outfields, $subtree);

Run an ADSI query. Simplyfies access to ADODB.Connection and ADODB.Command.
The filter hash takes properties to search for. Desired output fields can
be specified in the outfields array. Set subtree to true to search in the
entire domain or OU.

$domain_ou can be specified as MY.DOMAIN.NAME/OU/SUB_OU

Returns a reference to an array of hashes, each hash having the
selected output fields as keys.

=cut

sub ADSIQuery {
my($Domain, $filter_hash, $outfields, $subtree) = @_;

my($dom, $ou) = split('/', $Domain, 2);

my $ADsPath = 'LDAP://';
$ADsPath .= join(',', map { "OU=" . $_ } split('/', $ou)) . ',' if $ou;
$ADsPath .= join(',', map { "DC=" . uc } split(/\./, $dom));

my $filter = '(&';
$filter .= join(
'',
map {
"($_=" . $filter_hash->{$_} . ')'
} keys(%$filter_hash)
);
$filter .= ')';

my $fields = join(',', @$outfields);
my $cmd = join(';', "<$ADsPath>", $filter, $fields, $subtree ? 'subtree' : ());

my $o_conn = Win32::OLE->new("ADODB.Connection") or do {
$ADSILastErr = "Cannot create ADODB object!";
return;
};
my $o_cmd = Win32::OLE->new("ADODB.Command") or do {
$ADSILastErr = "Cannot create ADODB command!";
return;
};

$o_conn->{Provider} = "ADsDSOObject";
$o_conn->{ConnectionString} = "Active Directory Provider";
$o_conn->Open();
$o_cmd->{ActiveConnection} = $o_conn;
$o_cmd->{CommandText} = $cmd;

my $o_rec = $o_cmd->Execute() or do {
$ADSILastErr = "Cannot execute ADODB command!";
return;
};

unless ( $o_rec ) {
$ADSILastErr = join("\n", values(%{$o_conn->Errors()}));
return;
}

my @records;

while ( ! $o_rec->EOF ) {
push( @records, { map { $_ => $o_rec->Fields($_)->value } @$outfields });
$o_rec->MoveNext;
}

$o_rec->Close;
$o_conn->Close;

return(\@records);
}


=head2 ADSISetParam($path, %param_hash);

sets and saves a set of parameters for the ADSI object at $path;

Returns true on success or undef on error.

=cut

sub ADSISetParam {
my $path = shift;

unless ( 0 == @_ % 2 ) {
$ADSILastErr = 'ADSISetParam: odd number of params';
return;
}

my %params = @_;

my $obj = Win32::OLE->GetObject($path) or do {
$ADSILastErr = Win32::FormatMessage(Win32::LastErr());
return;
};

foreach my $p ( keys(%params) ) {
$obj->Put($p, $params{$p});
}

$obj->SetInfo() && do {
$ADSILastErr = Win32::FormatMessage(Win32::GetLastError());
return;
};

return(1);
}


=head2 ADSIGetGroupMembersRecursive($path);

Gets all members of a group, including those that get membership
through another group. Also lists users who have this group as
primary group (It is a M$ "feature" not to treat those users as
members of the group).

Returns a reference to a hash with the member name as the key and the
displayName as value.

=cut

sub ADSIGetGroupMembersRecursive {
my($group_path) = @_;

# retrieving the group object
my $grp_obj = Win32::OLE->GetObject($group_path) or do {
$ADSILastErr = Win32::FormatMessage(Win32::GetLastError());
return;
};

my @group_stack = ($grp_obj);

my(%members, %seen);
while ( @group_stack ) {

my $obj = shift(@group_stack);
my $name = $obj->Get('Name');

# avoid endless recursion
next if $seen{$name};
$seen{$name} = 1;

my $users_with_pg = ADSIGetUsersWithPrimaryGroup($obj) || [];

$members{$_->{Name}} = $_ for @$users_with_pg;

foreach my $mem ( in($obj->Members) ) {

my $name = $mem->Get('Name');
my $class = $mem->Class;
my $path = $mem->AdsPath;

if ( $class eq 'group' ) {

push @group_stack, $mem;

}
else {

$members{$name} = {
displayName => $mem->Get('displayName') || '',
ADsPath => $path,
};

}

}

}

return(\%members);
}


=head2 ADSIGetUsersWithPrimaryGroup($grp_obj);

Gets all users who have set the group as primary group.

$grp_obj is a ADSI group object. It can be created for example by:

$grp_obj = Win32::OLE->GetObject(<LDAP_PATH>);

Returns a reference to a hash with the member name as the key and the
displayName as value.

=cut

sub ADSIGetUsersWithPrimaryGroup {
my($grp_obj) = @_;

$grp_obj->GetInfoEx(['canonicalName', 'primaryGroupToken'], 0);
my $cn = $grp_obj->GetEx('canonicalName')->[0];
my $domain = (split('/', $cn, 2))[0];
my $pg = $grp_obj->GetEx('primaryGroupToken')->[0];

my $users_with_pg = ADSIQuery(
$domain,
{qw/objectClass user/, primaryGroupID => $pg},
[qw/ADsPath Name displayName/],
1,
) or do {
$ADSILastErr = Win32::FormatMessage(Win32::GetLastError());
return;
};

return($users_with_pg);
}



=head1 AUTHOR

Thomas Kratz <ThomasKratz@xxxxxx>

Copyright (c) 2006 Thomas Kratz. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=cut


1;


Relevant Pages

  • Re: question(s) re: win32::ole and active-directory interaction
    ... I have attached a module I wrote to simplify ADSI queries. ... our $ADSILastErr = ''; ... =head1 VERSION ... Gets all members of a group, ...
    (comp.lang.perl.modules)
  • Re: NetQueryDisplayInformation Question
    ... > enumerating information from Active Directory its self rather than having ... > connecting to the local machine and finding out who the members are of its ... > Would the ADSI still work for me in this situation? ... one is the LDAP provider the other is the WinNT provider. ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Strange ADAM behavior
    ... > groups that have only a few members that are displaying the same behavior. ... > software and adsi edit. ... > something to do with the replication. ... >>> We have an adam instance that is replicated to another adam. ...
    (microsoft.public.windows.server.active_directory)
  • Re: Strange ADAM behavior
    ... members are in the group, but that you just don't see them. ... > We have an adam instance that is replicated to another adam. ... > when you view the membership through adsi the user is not there. ... > able to prove or disprove the relationship to replication. ...
    (microsoft.public.windows.server.active_directory)