#!D:/Perl/bin/perl.exe -T
##
my $VERSION = 20010222;
use CGI qw( -no_debug -newstyle_urls :standard escapeHTML escape);
use CGI::Carp 'fatalsToBrowser';
$| = 1; use strict; use integer; # use 5.005; I used 5.005... not certain of min req
use Data::Dumper; $Data::Dumper::Indent = 1;
use Time::Seconds;
use Win32::AdminMisc;
use Win32;
use Win32::OLE;
use Win32::OLE::Enum;
use Win32::OLE::Variant;
# POD added at the last minute per
# http://www.cpan.org/scripts/submitting.html
=head1 NAME
adsibrowser - cgi script browses ActiveDirectory via the web
=head1 DESCRIPTION
this is basically a learning excersize for me to wrap my head
around adsi and what it makes available. When I started there
wasn't much out there on perl and ADSI (aside from TE's perl/Tk
adsi browser for which I had not the Tk) so I thought other
folks might be interested in the code, rough as it is.
=head1 README
install cgi script, pull it up in your web browser, pick a
level of detail and starting point, view ADSI objects, their
property values, available methods/properties, implemented
interfaces. Click links to see more.
=head1 PREREQUISITES
ADSI must be installed on the web server
http://www.microsoft.com/adsi
Perl modules:
CGI
CGI::Carp
Data::Dumper
Time::Seconds
Win32::AdminMisc
Win32
Win32::OLE
Win32::OLE::Enum
Win32::OLE::Variant
=head1 COREQUISITES
none
=pod OSNAMES
winNT (developed on NT4, I think Win2K might work too)
=pod SCRIPT CATEGORIES
Win32
=pod CHANGES
20010127
first release
20010222
fixed broken "custom path" feature.
I'd added that at the last minute before uploading, and neglected to
test it (or even use it). As I said, this code is just gathering dust
on my end. I'm glad "A.C." has some use for it and spotted the bug too.
=cut
##
# script to browse adsi hierarchies (read only) via the web
# writen as an exercise to learn about adsi, and eventually to be a useful tool
# the script's documentation is nearly non-existant
#
# released into the public domain by the author (matthew wickline) Jan 26 2001
##
# DO NOT install this script w/o restricting access to those who should have it.
# DO NOT install this script unless you accept full liability for any and all
# consequences, direct and indirect, of the script's use. It's good enough for
# what I want it to do, but you need to verify for yourself that it doesn't
# pose any sort of risk for you.
##
# I've got .plt mapped on my webserver to run perl -T
# You may want to remove the -T up there, and/or change the filename.
##
# This is not polished. At all. This is the first ADSI, or even OLE thing
# that I've done. It's hugely sluggish, inneficient, etc... it's a work in
# progress. I don't know when/if I'll do more with it, but thought I would
# put this out in the public for folks to chew on since it did have some
# code folks might be able to adapt to their needs. Note that I've only got
# the winnt namespace (as avail in NT4) to play with, so any of those other
# namespaces are given no attention in the code that follows. In my world,
# the LDAP and IIS namespaces are leaf nodes... no objects within.
##
# I've stripped out a bunch references to site-specific modules that handle
# errors, logging, page look-and-feel, etc. A bit of the code's problems are
# due to less-than-ideal substitutions as I try to get something together
# that will work elsewhere. Hopefully you now just need perl and ADSI.
# (and some modules that I think come with activeperl by default)
# perl: http://www.activestate.com/Products/ActivePerl/
# adsi: http://www.microsoft.com/adsi
# Oh! I think Time::Seconds is *not* standard...
# cpan: http://search.cpan.org/search?mode=module&query=time%3A%3Aseconds
# Hopefully I didn't miss any problems after excising the site-specific code
##
# Credits:
#
# Toby Everett http://opensource.activestate.com/authors/tobyeverett/
# I used his interface data before I broke down and parsed my own.
# His emails also helped me wrap my head around a few issues.
#
# http://www.asptoday.com/articles/19990310.htm
# Alerted me to the lanmanserver issue (search for 'lanmanserver' below).
##
my $groups_members_as_properties = 1;
# Users have a method 'Groups' which tells you what groups they're in
# Groups have a method 'Members' which tells you what users they have
# There didn't seem to be many other methods that were very usefull in
# read only sense, so set the above flag to treat those two methods as
# properties, for the purposes of this script.
my $live_version = 0;
# developmental version ($live=0) uses Data::Dumper to show some stuff
# live version has less cluttered output
# repeat var names to avoid spurious 'used once' warning
$CGI::DISABLE_UPLOADS = $CGI::DISABLE_UPLOADS = 1;
$CGI::POST_MAX = $CGI::POST_MAX = 1024*1024*.25; # .25Mb is plenty
&set_title( 'ADSI Browser' );
&set_heading( 'an ADSI browser, perl implementation, CGI interface' );
&bail_out( q[you probably don't want the general public running this (line ].__LINE__.')' )
unless $ENV{'REMOTE_USER'};
my %cache; # will be filled as things are looked up
# need to tie to disk, speed up script quite a bit
# if this script is ever used for "real" stuff, need to worry about stale data
$cache{'primary_interface'} = {
'Schema' => 'IADs',
'Namespace' => 'IADsNamespaces',
map {($_ => "IADs$_")} qw{Class Syntax Property Namespaces},
};
my %param = CGI::Vars();
if ( $live_version and !exists $param{'reduced_interface'} ) {
$param{'reduced_interface'} = 1; # default for non-dev
}
if ( exists $param{'path'} and $param{'path'} eq '*' ) {
# user indicated that they want to start at their own path
$param{'path'} = $param{'custom_path'};
param( '-name' => 'path', '-value' => $param{'custom_path'} );
}
my @info; # sorted list of keys below. to re-sort, drag-n-drop the code chunks
my %info = ( # idsip = info display
do{push(@info,'idisp_contained_objects');$info[-1]} => {
'hidden' => 0,
'desc' => q{list any objects contained by the node (takes a while if the node is a domain or a computer)},
'indent' => 0,
'checked' => 0,
'display' => \&idisp_contained_objects,
},
do{push(@info,'idisp_interface_hierarchy');$info[-1]} => {
'hidden' => $param{'reduced_interface'},
'desc' => q{list the interfaces supported by the object},
'indent' => 0,
'checked' => 1,
'display' => \&idisp_interface_hierarchy,
},
do{push(@info,'idisp_interface_details');$info[-1]} => {
'hidden' => $param{'reduced_interface'},
'desc' => q{describe the interfaces in more detail},
'indent' => 1,
'checked' => 0,
'display' => \&idisp_interface_details,
},
do{push(@info,'idisp_property_declarations');$info[-1]} => {
'hidden' => $param{'reduced_interface'},
'desc' => q{describe the properties applicable to the object},
'indent' => 0,
'checked' => 1,
'display' => \&idisp_property_declarations,
},
do{push(@info,'idisp_property_values');$info[-1]} => {
'hidden' => 0,
'desc' => q{list the object's properties' values (and other details)},
'indent' => !$param{'reduced_interface'},
'checked' => $param{'reduced_interface'},
'display' => \&idisp_property_values,
},
do{push(@info,'idisp_method_declarations');$info[-1]} => {
'hidden' => $param{'reduced_interface'},
'desc' => q{describe the methods applicable to the object},
'indent' => 0,
'checked' => 1,
'display' => \&idisp_method_declarations,
},
do{push(@info,'idisp_safe_method_values');$info[-1]} => {
'hidden' => 0,
'desc' => q{list the values of 'psuedoproperty' methods (best with user and group objects)},
'indent' => !$param{'reduced_interface'},
'checked' => 0,
'display' => ( $groups_members_as_properties ? undef : \&idisp_safe_method_values ),
},
do{push(@info,'idisp_tlb_data');$info[-1]} => {
'hidden' => $param{'reduced_interface'},
'desc' => q{dump all data currently parsed from type lib (takes a while)},
'indent' => 0,
'checked' => 0,
'display' => \&idisp_tlb_data,
},
);
@info = grep {
$info{$_}{'display'} # must be capable of rendering itself
and # mustn't be hidden by reduced interface preference
( $param{'reduced_interface'} ? !$info{$_}{'hidden'} : 1 )
} @info;
my $nothing_checked_alert;
my $number_checked = grep { exists($param{$_}) and $param{$_} } @info;
unless ( $number_checked ) {
if ( exists $param{'path'} ) {
$nothing_checked_alert
= q{None of these were checked, so I'll help you out... };
}
$param{$_}++ for (grep {$info{$_}{'checked'}} @info)
}
my $adsobj;
my $no_object_alert = '';
if ( $param{'path'} and length($param{'path'}) ) {
$param{'path'} =~ m/^([\s\S]+)$/;
$param{'path'} = $1; # untainted, bad data will fail in next line:
$param{'path'} =~ s{^([^:]+:),[^,]+$}{$1}; # bad to call a namespace what it is
unless ( $adsobj = &get_adsi_object_from_path( $param{'path'} ) ) {
$no_object_alert
= strong('Requested Object Not Found!');
}
}
if ( $no_object_alert or !( $param{'path'} and length $param{'path'} ) ) {
# bogus request, or new session: give them some good starting points...
my $local_node = Win32::NodeName() or &bail_out(
"NodeName() failed: $^E (line ".__LINE__.')',
);
my $domain = Win32::DomainName() or &bail_out(
"DomainName() failed: $^E (line ".__LINE__.')',
);
my $pdc_node = Win32::AdminMisc::GetPDC($domain) or &bail_out(
"GetPDC() failed: $^E (line ".__LINE__.')',
);
$pdc_node =~ s{^\\\\}{};
my $local_user = Win32::LoginName() or &bail_out(
"LoginName() failed: $^E (line ".__LINE__.')',
);
my $found_path = '';
print header, get_html_header(), (
$no_object_alert,
start_form(
'-action' => url(),
'-method' => 'GET',
),
dl(
dt( q{Choose the information you would like to view:} ),
dd( &config_view_prefs_for_path() ),
dt( q{ Choose a starting point: (derived from current environment)} ),
dd(join( '
',
(
map { join( '',
escapeHTML( $_->{'desc'} ),
q{ },
qq{ },
escapeHTML( $_->{'path'} ),
q{},
)} (
{
'path' => q{ADs:},
'desc' => q{root node of the ActiveDirectory hierarchy},
},{
'path' => qq{WinNT://$domain,domain},
'desc' => q{node representing the current domain},
},{
'path' => qq{WinNT://$domain/$local_user,user},
'desc' => q{node representing the current user},
},{
'path' => qq{WinNT://$domain/$local_node,computer},
'desc' => q{node representing this webserver },
},{
'path' => qq{WinNT://$domain/$pdc_node,computer},
'desc' => q{node representing that domain's PDC},
},
)
),
join( '',
q{node represented by a path of your choice:},
q{ },
q{ },
textfield(
'-name' => 'custom_path',
'-size' => 50,
'-maxlength' => 5000,
),
),
)),
dt( q{ Save view preferences and...} ),
dd( submit( '-value' => 'View Selected Node' ) ),
),
end_form(), end_html(),
);
exit();
} # else...
# good request, act on it:
my $interfaces;
my $uuid;
&set_tlb_info(); # sets the above two hashrefs will all their goodies
&approve_methods_as_psuedoproperties();
&set_title( 'Browsing ' . escapeHTML( $param{'path'} ) );
&set_heading( 'ADSI Path ' . &all_partial_links_for_path( $param{'path'} ) );
print header, &get_html_header(), join( '
',
config_view_prefs_for_path( $param{'path'} ),
map { $param{$_}
? &{ $info{$_}{'display'} }( $adsobj )
: ()
} @info
), end_html();
exit();
sub bail_out {
&set_title( 'Error!' );
&set_heading( $_[0] );
print header &get_html_header(), p(q{ Please try again later. }), end_html();
exit();
}
{
my $web_page_title;
my $web_page_heading;
my $web_page_html_header;
sub get_html_header {
$web_page_html_header
}
sub set_heading {
$web_page_html_header = start_html($web_page_title).strong($_[0])
}
sub set_title {
$web_page_html_header = start_html($_[0]).strong($web_page_heading)
}
}
sub info_link_for_path {
# given ads path, create href to link to path with current info settings
return escapeHTML join( '',
url(),
'?',
join( ';',
'path=' . &escape($_[0]),
(
map {
&escape($_) . "=" . &escape($_[0])
} keys %info, 'reduced_interface'
),
),
);
}
sub all_partial_links_for_path {
# return a string with links to all partial paths
# only arg is adspath
my $adspath = shift;
my $adsobj;
unless (
$adspath =~m/^([^:]+:.*?)(?:,([^,]+))?$/
and $adsobj = &get_adsi_object_from_path( $adspath )
) {
return escapeHTML($adspath);
}
my($path, $class) = ( $1, $adsobj->{'Class'} || $2 );
my $partial_path = '';
my $return = ''.escapeHTML($path).'';
if ( $path =~ m{^(([^:]+:)//([^/]+))(?:/(.+))?} ) {
$return = join( '',
'',escapeHTML( $2 ),'',
'// ',
'',escapeHTML( $3 ),'',
);
$partial_path = $1;
for my $piece ( $4 ? split('/', $4) : () ) {
$partial_path .= '/'.$piece;
$return .= '/ '
. escapeHTML($piece)
. ''
;
}
}
return $return . ( $class
? ', ' . &get_class_link($adsobj)
: ''
);
}
sub get_class_link {
# tries to return link to class of the adsobj at a given path
# only arg is adsobj
my $class_schema = &get_class_schema( $_[0] );
unless ( $class_schema ) {
return escapeHTML( $_[0]->{'Class'} )
}
return join( '',
'',
$_[0]->{'Class'},
'',
);
}
sub get_property_or_syntax_link {
# tries to return link to property, or just that property
# only arg is a string... maybe a an adspath, maybe a property name
my $adsi_object = &get_adsi_object_from_path( $_[0] );
return( $adsi_object
? join( '',
'',
escapeHTML( $adsi_object->{'Name'} ),
'',
)
: escapeHTML( $_[0] )
);
}
sub config_view_prefs_for_path {
# return form to turn on/off display of certain items
# only arg is typically an ads path
# if arg not defined, that means we return
# just the checkboxes, not the whole form
my $path = shift; # only arg is an adsi path
return join( '',
(
!defined $path ? '' : start_form(
'-action' => url(),
'-method' => 'GET',
)
),
( !defined $path ? () : (
' ',
)),
$nothing_checked_alert,
'',
(
map {(
' ' x $info{$_}{'indent'},
qq{ ',
( $path and $param{$_} and $number_checked > 1
? qq{ (goto) } : ''
),
escapeHTML( $info{$_}{'desc'} ),
' ',
)} @info
),
'',
(
map {(
qq{},
)} grep { # possible to display, but currently hidden
$info{$_}{'display'} and $info{$_}{'hidden'}
} keys %info
),
(
!defined $path ? '' : (
hidden( '-value' => $path, '-name' => 'path', ),
submit( '-value' => 'Save Settings and Refresh View', ),
' (unsaved settings are ignored)',
end_form(),
)
),
);
}
sub get_adsi_object_from_path {
my $path = shift; # only arg is an adsi path
unless ( exists ${ $cache{'object'} }{$path} ) {
${ $cache{'object'} }{$path} = Win32::OLE->GetObject( $path );
}
return ${ $cache{'object'} }{$path};
}
sub get_schema {
# only arg is an adsi object
if ( exists ${ $cache{'schema'} }{ $_[0]->{'AdsPath'} } ) {
return( ${ $cache{'schema'} }{ $_[0]->{'AdsPath'} } );
}
my $class_schema = &get_class_schema( $_[0] );
if ( $class_schema ) {
my $schema = $class_schema->{'Parent'};
if ($schema and
$schema = &get_adsi_object_from_path( $class_schema->{'Parent'} )
) {
return( ${ $cache{'schema'} }{ $_[0]->{'AdsPath'} } = $schema );
}
}
# no schema found yet... see if we can guess it:
if ( $_[0]->{'AdsPath'} =~ m{^(WinNT://[^/,]+)} ) {
# cache a guess if we don't have one yet... (guess may even be undef)
unless ( exists ${ $cache{'schema'} }{' default '}{$1} ) {
${ $cache{'schema'} }{' default '}{$1}
= &get_adsi_object_from_path( qq{$1/Schema,Schema} );
}
return (
${ $cache{'schema'} }{ $_[0]->{'AdsPath'} }
= ${ $cache{'schema'} }{' default '}{$1}
);
} else {
my $domain = Win32::DomainName() or &bail_out(
"DomainName() failed: $^E (line ".__LINE__.')',
);
unless ( exists ${ $cache{'schema'} }{' default '}{$domain} ) {
${ $cache{'schema'} }{' default '}{$domain}
= &get_adsi_object_from_path( qq{WinNT://$domain/Schema,Schema} );
}
return (
${ $cache{'schema'} }{ $_[0]->{'AdsPath'} }
= ${ $cache{'schema'} }{' default '}{$domain}
);
}
# drats... we failed :(
return undef;
}
sub get_class_schema {
# only arg is an adsi object
if ( exists ${ $cache{'class_schema'} }{ $_[0]->{'AdsPath'} } ) {
return( ${ $cache{'class_schema'} }{ $_[0]->{'AdsPath'} } );
}
my $class_schema = $_[0]->{'Schema'};
if ($class_schema and
$class_schema = &get_adsi_object_from_path( $_[0]->{'Schema'}.',Class' )
) {
return( ${ $cache{'class_schema'} }{ $_[0]->{'AdsPath'} } = $class_schema );
}
# no class schema found yet... see if we can guess it:
if ( $_[0]->{'AdsPath'} =~ m{^(WinNT://[^/,]+)} ) {
# cache a guess if we don't have one yet... (guess may even be undef)
my $guess_path = $1.'/Schema/'.$_[0]->{'Class'}.',Class';
unless ( exists ${ $cache{'class_schema'} }{' default '}{$guess_path} ) {
${ $cache{'class_schema'} }{' default '}{$guess_path}
= &get_adsi_object_from_path( $guess_path );
}
return (
${ $cache{'class_schema'} }{ $_[0]->{'AdsPath'} }
= ${ $cache{'class_schema'} }{' default '}{$guess_path}
);
}
# drats... we failed :(
return undef;
}
sub get_primary_interface {
# only arg is an adsi object
if ( exists ${ $cache{'primary_interface'} }{ $_[0]->{'Class'} } ) {
return( ${ $cache{'primary_interface'} }{ $_[0]->{'Class'} } );
}
my $p_interface ;
my $class_schema;
if ( $class_schema = &get_class_schema( $_[0] ) ) {
( $p_interface = $class_schema->{'PrimaryInterface'} ) =~ s/[\{\}]//g;
$p_interface = $uuid->{ $p_interface };
}
unless ( $p_interface ) {
# what the hell... just try everything we know about
unless ( exists ${ $interfaces }{'__uber_interface__'} ) {
$interfaces->{'__uber_interface__'}{'isa'} = [keys %$interfaces];
}
$p_interface = '__uber_interface__';
}
return ${ $cache{'primary_interface'} }{ $_[0]->{'Class'} } = $p_interface;
}
sub idisp_contained_objects {
# return a view of what stuff might be inside an adsi object
# only arg is an adsi object
return &pretty_value_contained_objects($_[0]);
# had to extract this code for other subs to use... ah well...
}
sub idisp_interface_hierarchy {
my @isa = reverse @{ $interfaces->{ &get_primary_interface($_[0]) }{'isa'} };
my $return = join( '',
h2(q{Supported Interfaces:}),
shift(@isa),
' ',
);
my $indent;
for my $i ( @isa ) {
$return .= join( '',
'',
' ' x $indent++,
'`-->',
escapeHTML($i),
);
my @things;
if ( exists ${ $interfaces->{$i} }{'methods'} ) {
push( @things,
scalar( keys %{ $interfaces->{$i}{'methods'} } )
. ' ' . (
keys %{ $interfaces->{$i}{'methods'} } == 1
? 'method'
: 'methods'
)
);
}
if ( exists ${ $interfaces->{$i} }{'properties'} ) {
push( @things,
scalar( keys %{ $interfaces->{$i}{'properties'} } )
. ' ' . (
keys %{ $interfaces->{$i}{'properties'} } == 1
? 'property'
: 'properties'
)
);
}
$return .= !@things ? ' '
: ' (provides ' . join( ' and ', @things ) . ') ';
}
return $return;
}
sub idisp_interface_details {
my @isa = @{ $interfaces->{ &get_primary_interface($_[0]) }{'isa'} };
my $return = join( '',
h2(qq{Interface Details:}),
'
',
);
}
sub idisp_safe_method_values {
my $p_interface = &get_primary_interface( $_[0] );
my %methods;
for my $i ( @{ $interfaces->{$p_interface}{'isa'} } ) {
@methods{
grep { $interfaces->{$i}{'methods'}{$_}{'psuedoproperty'}
} keys %{ $interfaces->{$i}{'methods'} }
} = ();
}
my $methhod_s = scalar( keys %methods ) == 1 ? 'Method' : 'Methods';
unless ( keys %methods ) {
return h2(
qq{Contains no Property-like Methods!}
);
}
# need to pre-process data to render it attractively
# need to extract rendering code from get_property_hashes to share here
# this will simplify $quiet in idisp_contained_objects
my $return = join( '',
h2(
qq{}
. scalar( keys %methods ),
' Property-like ',
$methhod_s.':',
),
'
',
(
map {
my $m_name = $_;
my $value = $_[0]->{$m_name};
my $default_display = pre( escapeHTML( &Dumper( $value ) ) );
my $display;
if ( ref $value and ref $value eq 'Win32::OLE' ) {
$display = &pretty_value_contained_objects( $value )
} elsif ( !defined($value) or !length($value) ) {
$display = ' ';
} else {
$display = qq{$default_display};
}
(
'
',
$m_name,
'
',
$display,
( $live_version ? '' : "
$default_display"),
'
',
);
} sort keys %methods
),
'
',
);
}
sub idisp_tlb_data {
return join( '',
h2(
qq{Dump of Parsed Type Lib Info:}
),
pre(escapeHTML(Dumper( $interfaces, $uuid ))),
);
}
sub get_property_hashes {
# given an adsi object, return a hash of property hashes
# only arg is an adsi object
my $p_interface = &get_primary_interface( $_[0] );
my %properties;
my %property_interfaces;
for my $i ( @{ $interfaces->{$p_interface}{'isa'} } ) {
@properties{ keys %{ $interfaces->{$i}{'properties'} } } = ();
@property_interfaces{ keys %{ $interfaces->{$i}{'properties'} } }
= ( values %{ $interfaces->{$i}{'properties'} } );
}
if ( $groups_members_as_properties ) {
$properties{'Groups'}++ if $_[0]->{'Class'} eq 'User';
$properties{'Members'}++ if $_[0]->{'Class'} eq 'Group';
}
my $properties = {
map { $_ => { 'reqd' => 'Unknown' } }
keys %properties
};
my $class_schema;
if ( $class_schema = &get_class_schema( $_[0] ) ) {
for my $p_type (qw{ Optional Mandatory }) {
if ( defined $class_schema->{$p_type.'Properties'} ) {
for my $p_name ( @{ $class_schema->{$p_type.'Properties'} } ) {
next unless defined $p_name and length $p_name;
# don't know why, but MS has some empties in these lists!
$properties->{$p_name}{'reqd'} = $p_type;
}
}
}
}
my $schema = &get_schema( $_[0] );
for my $p_name ( keys %$properties ) {
# start with the property name
$properties->{$p_name}{'property'} = $p_name;
# we'll create a link to get info later if possible
# now try to find out the type for the property (and syntax for that type)
if ($schema) { # need a $schema to do that properly
# look in the normal place first:
my $try_path = $schema->{'AdsPath'}.'/'.$p_name.',Property';
my $try;
if ( $try = &get_adsi_object_from_path( $try_path ) ) {
# great... turn the property into a path to more info on the property:
# that path will later be changed into a link
$properties->{$p_name}{'property'} = $try_path;
$try_path = $schema->{'AdsPath'}.'/'.$try->{'Syntax'}.',Syntax';
$try = &get_adsi_object_from_path( $try_path );
# cool... now we can have a path for syntax info too:
$properties->{$p_name}{'syntax'} = $try_path if $try;
} else {
# not in the normal place, so guess:
my %syntax_guesses = (
( map {($_ => 'ADsPath')} qw{ADsPath Parent Schema} ),
( map {($_ => 'String') } qw{Class Name} ),
);
$try_path = $schema->{'AdsPath'}.'/'.$syntax_guesses{$p_name}.',Syntax';
$try = &get_adsi_object_from_path( $try_path );
$properties->{$p_name}{'syntax'} = $try_path if $try;
$properties->{$p_name}{'syntax_guessed'} = 1 if $syntax_guesses{$p_name};
}
my $syntax = &get_adsi_object_from_path( $properties->{$p_name}{'syntax'} );
if ( $syntax ) {
$properties->{$p_name}{'type'}
= $syntax->{'Name'} || 'unknown ('.__LINE__.')';
} else {
$properties->{$p_name}{'type'} = 'undef ('.__LINE__.')';
}
}
# GetInfoEx ensures property value will be in cache to fetch when we want:
$_[0]->GetInfoEx( [$p_name], 0 );
$properties->{$p_name}{'value'} = $_[0]->{$p_name};
unless ( $properties->{$p_name}{'value'} ) {
# if we didn't get a value the easy way, try explicit Get call:
my $try = $_[0]->Get($p_name);
# use intermediate $try to avoid overwriting an earlier 0 with undef
if ( ($properties->{$p_name}{'reqd'} ne 'Unknown') and (
( defined $try and !defined $properties->{$p_name}{'value'} )
or
( $try and !$properties->{$p_name}{'value'} )
)
) {
$properties->{$p_name}{'value'} = $try;
}
}
# stash a copy for safe keeping... the above gets processed for display
$properties->{$p_name}{'_safe_value_'} = $properties->{$p_name}{'value'};
# notes will hold a geeky representation for my use in testing stuff:
$properties->{$p_name}{'notes'} = pre( escapeHTML(
&Dumper( $properties->{$p_name}{'value'} ),
));
# need to figure out how to display some of the more opaque property values
# also need to extract more of this display code into subs, like
# pretty_value_interval
# But for now, the bulk of that sort of code is as follows:
if ( ref $properties->{$p_name}{'value'} ) {
if ( ref $properties->{$p_name}{'value'} eq 'Win32::OLE' ) {
$properties->{$p_name}{'value'}
= &pretty_value_contained_objects( $properties->{$p_name}{'value'} );
} elsif ( ref $properties->{$p_name}{'value'} eq 'ARRAY' ) {
my %type_guess = (
'Containment' => 'Class',
'PossibleSuperiors' => 'Class',
'MandatoryProperties' => 'Property',
'OptionalProperties' => 'Property',
);
$properties->{$p_name}{'value'} = join( qq{ \n}, (
$schema && scalar( grep {$_ eq $p_name} (keys %type_guess) )
? (
map {
my $try_path
= $schema->{'AdsPath'}.'/'.$_.','.$type_guess{$p_name};
&get_adsi_object_from_path( $try_path )
? &get_property_or_syntax_link( $try_path )
: $_
;
} @{ $properties->{$p_name}{'value'} }
)
: (
map { escapeHTML($_) }
@{ $properties->{$p_name}{'value'} }
)
),);
} elsif (
ref $properties->{$p_name}{'value'} eq 'Win32::OLE::Variant'
and $properties->{$p_name}{'type'} eq 'Time'
) {
$properties->{$p_name}{'value'} = escapeHTML(
$properties->{$p_name}{'value'}
# .'('.${ $properties->{$p_name}{'value'} }.')' # shows ticks
);
} else {
# ref of type we don't know how to handle yet!
$properties->{$p_name}{'value'}
= escapeHTML( $properties->{$p_name}{'value'} );
}
} elsif ( $properties->{$p_name}{'type'} eq 'ADsPath' ) {
$properties->{$p_name}{'value'}
= &all_partial_links_for_path( $properties->{$p_name}{'value'} )
} elsif (
$_[0]->{'Class'} eq 'FileShare' and (
$p_name eq 'Path'
or
$p_name eq 'Name'
) and ( $properties->{$p_name}{'value'} !~ /a href/ )
) {
my($server) = ( $_[0]->{'Parent'} =~ m{^[^:]+://[^/]+/([^/]+)/} );
$properties->{$p_name}{'value'} = join( '',
'',
escapeHTML( $properties->{$p_name}{'value'} ),
'',
);
my $href = $properties->{$p_name}{'value'};
} elsif (
$properties->{$p_name}{'value'} =~ m{^\\\\[^\\]+}
and $properties->{$p_name}{'type'} eq 'Path'
) {
my $href = $properties->{$p_name}{'value'};
$href =~ s{\\}{/}g;
$properties->{$p_name}{'value'} = join( '',
'',
escapeHTML( $properties->{$p_name}{'value'} ),
'',
);
} elsif ( $p_name eq 'Class' and $properties->{$p_name}{'value'} ) {
$properties->{$p_name}{'value'} = &get_class_link( $_[0] )
} elsif (
$properties->{$p_name}{'value'}
and
$properties->{$p_name}{'value'} > 0
and (
$properties->{$p_name}{'type'} eq 'Time'
or
$properties->{$p_name}{'type'} eq 'Interval'
)
) {
$properties->{$p_name}{'value'} = &pretty_value_interval(
$properties->{$p_name}{'value'}
);
} elsif ( $schema and $p_name eq 'Syntax' and $_[0]->{'Class'} eq 'Property' ) {
my $try_path
= $schema->{'AdsPath'}.'/'.$properties->{$p_name}{'value'}.',Syntax';
$properties->{$p_name}{'value'} = &get_property_or_syntax_link(
&get_adsi_object_from_path( $try_path )
? $try_path
: $properties->{$p_name}{'value'}
);
} else {
escapeHTML( $properties->{$p_name}{'value'} );
}
# if an unknown property looks like an IADs property, say so:
if ( $properties->{$p_name}{'reqd'} eq 'Unknown' ) {
# try to guess if we think we can
# for now, the following seems to work ok...
if ( grep {$_ eq $p_name} keys %{ $interfaces->{'IADs'}{'properties'} } ) {
$properties->{$p_name}{'reqd'} = 'IADs';
}
}
unless (
defined $properties->{$p_name}{'value'}
and length $properties->{$p_name}{'value'}
) {
# put *something* in there (NN 4.x won't color empty table cells)
$properties->{$p_name}{'value'} = ' ';
}
# link up property name to syntax, and type to type def:
$properties->{$p_name}{'property'}
= &get_property_or_syntax_link( $properties->{$p_name}{'property'} );
$properties->{$p_name}{'syntax'} = ( $properties->{$p_name}{'syntax'}
? join( '',
( $properties->{$p_name}{'syntax_guessed'} ? '(' : '' ),
'',
$properties->{$p_name}{'type'},
'',
( $properties->{$p_name}{'syntax_guessed'} ? ')' : '' ),
)
: '?'
);
if ( exists ${ $property_interfaces{$p_name} }{'set'} ) {
$properties->{$p_name}{'property'}
= strong( $properties->{$p_name}{'property'} );
}
} # repeat for each property
return $properties;
}
sub pretty_value_contained_objects {
# return a view of what stuff might be inside an adsi object
# only arg is an adsi object
my $quiet = (caller(1))[3] ne 'main::idisp_contained_objects';
# quite output (no h2 and hr tags) for anyone but the above
my $enum;
eval {$enum = Win32::OLE::Enum->new($_[0])};
my %contents;
if ( $enum ) {
for my $nested_object ( Win32::OLE::in $_[0] ) {
my $name = $nested_object->{'Name'};
my $class = $nested_object->{'Class'};
# in the href's below, we could call $nested_object->{'AdsPath'}
# to get the path, but that's much slower than manual construction
push( @{ $contents{ lc($class) } },
''
. escapeHTML( $name )
.''
);
# adsi won't show lanmanserver's most usefull identity
# through enumeration of its container, so 'out' it manually:
if ( lc($name) eq 'lanmanserver' and lc($class) eq 'service' ) {
push( @{ $contents{ lc('File'.$class) } },
''
. escapeHTML( $name )
.''
);
}
}
}
unless ( keys %contents ) {
# don't waste time building a table
return( $quiet ? '' : h2(
'Leaf Node: Contains Zero Objects',
));
}
my $total;
my $return = '