Created
October 19, 2012 10:19
-
-
Save Chaz6/3917348 to your computer and use it in GitHub Desktop.
Create organizational chart from Active Directory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl -w | |
######################################################################################## | |
# This script connects to a domain controller and produces an organizational chart. | |
# | |
# It is based on the script at the following page:- | |
# http://thelowedown.wordpress.com/2008/05/27/generate-calltree-from-active-directory/ | |
# | |
# Chris Hills ([email protected]) | |
# 2012/10/19 | |
######################################################################################## | |
######################################################################################## | |
# Configuration | |
######################################################################################## | |
# Domain controller | |
my $server = "example.com"; | |
# Bind user | |
my $bindDn = "cn=Administrator,cn=Users,dc=example,dc=com"; | |
# Base Dn | |
my $baseDn = "cn=Users,dc=example,dc=com"; | |
# Filter | |
my $filter = "(objectCategory=cn=Person,cn=Schema,cn=Configuration,dc=example,dc=com)"; | |
# Path to graphviz dot | |
my $DOT = "/usr/bin/dot"; | |
# Output format (supported by dot) | |
my $format = "svg"; | |
# Output prefix | |
my ${PREFIX} = "/tmp/ad"; | |
######################################################################################## | |
# Program | |
######################################################################################## | |
use strict; | |
use Net::LDAP; | |
use Net::LDAP::Control::Paged; | |
use Net::LDAP::Constant ( "LDAP_CONTROL_PAGED" ); | |
$| = 1; | |
my $trk_loaded = 0; | |
eval { | |
require Term::ReadKey; | |
import Term::ReadKey; | |
$trk_loaded = 1; | |
}; | |
print 'Password: '; | |
ReadMode('noecho') if $trk_loaded; # input echo off | |
chomp(my $bindPw = $trk_loaded ? ReadLine(0) : <STDIN>); | |
ReadMode('restore') if $trk_loaded; # input echo on | |
print "\n"; | |
my $ldap = Net::LDAP->new($server) or die "Cannot connect to LDAP server: $@\n"; | |
my $mesg = $ldap->bind( | |
dn => $bindDn, | |
password => $bindPw, | |
) or die "Cannot bind to ldap: $!\n";; | |
if($mesg->code){ | |
print "LDAP bind failed: " . $mesg->error_text . "\n"; | |
exit(1); | |
} | |
my %calltree; | |
my %user; | |
my $cookie; | |
my $page = Net::LDAP::Control::Paged->new(size => 999) or die $!; | |
my $attribs = [ "sAMAccountname","displayName","mobile","manager","directReports","distinguishedName","name","telephoneNumber","ipphone","mobile","title","department" ]; | |
my @args = ( | |
base => $baseDn, | |
scope => 'sub', | |
filter => $filter, | |
attrs => $attribs, | |
pagesize => 800, | |
control => [$page] | |
); | |
print "\n"; | |
print "Searching ldap...\n"; | |
while(1) { | |
# Perform search | |
my $mesg = $ldap->search( @args ); | |
if($mesg->code){ | |
print "LDAP search failed: " . $mesg->error_text . "\n"; | |
exit; | |
} | |
print "Found " . $mesg->count . " entries...\n"; | |
# Filter results | |
foreach my $entry ( $mesg->entries ) { | |
my @rec; | |
my $DN = $entry->get_value( "distinguishedName" ); | |
my $name = $entry->get_value( "displayName" ); | |
my $acct = lc $entry->get_value( "sAMAccountname" ); | |
my $mgr = $entry->get_value( "manager" ); | |
my $phone = $entry->get_value( "telephoneNumber" ) || $entry->get_value( "ipphone" ) || $entry->get_value( "mobile" ); | |
my $title = $entry->get_value( "title" ); | |
my $dept = $entry->get_value( "department"); | |
# Skip a few special cases | |
next if lc($name) eq $acct; | |
if (!defined $mgr) { $mgr = "NONE"; } | |
if (!defined $phone) { $phone = "UNKNOWN"; } | |
if (!defined $title) { $title = "UNKNOWN"; } | |
if (!defined $dept) { $dept = "UNKNOWN"; } | |
@rec = ($acct, $name, $phone, $mgr, $title, $dept); | |
# LDAP Attributes are multi-valued, so we have to get each one. | |
foreach my $subo ( $entry->get_value( "directReports" ) ) { | |
push (@rec, $subo); | |
} | |
$calltree{$DN} = [ @rec ]; # Hash by DN listing attrs and subordinates | |
$user{$acct} = $DN; # Hash for each account listing the DN | |
} | |
# Only continue on LDAP_SUCCESS | |
$mesg->code and last; | |
# Get cookie from paged control | |
my($resp) = $mesg->control( LDAP_CONTROL_PAGED ) or last; | |
$cookie = $resp->cookie or last; | |
# Set cookie in paged control | |
$page->cookie($cookie); | |
} | |
if ($cookie) { | |
# We had an abnormal exit, so let the server know we do not want any more | |
$page->cookie($cookie); | |
$page->size(0); | |
$ldap->search( @args ); | |
die("LDAP query unsuccessful"); | |
} | |
$ldap->unbind; | |
print "Finished with LDAP...\n"; | |
my @topGraph; | |
my @L1Graph; | |
# Top-level graph: no supervisor or reporting to person w/o supervisor | |
print "Writing ${PREFIX}-top.txt...\n"; | |
open (DOT, ">${PREFIX}-top.txt") or die "Could not open output file!"; | |
print DOT "digraph CT0 {\n"; # page=\"8.5,11\"\n margin=1\n"; | |
print DOT " rankdir=LR\n"; | |
foreach my $acct (sort keys %user) { | |
my $entry = $calltree{$user{$acct}}; | |
if ($entry->[3] eq "NONE") { | |
displayNode( $entry ); | |
push @topGraph, $user{$acct}; | |
if ($#{$entry} > 3) { | |
for my $i ( 4 .. $#{$entry} ) { | |
# Only add if listed subordinate is still an employee | |
if ($calltree{$entry->[$i]}) { | |
push @L1Graph, $entry->[$i]; | |
displayNode( $calltree{$entry->[$i]} ); | |
print DOT "\t\"$acct\" -> \"$calltree{$entry->[$i]}[0]\"\n"; | |
} | |
} | |
} | |
} | |
} | |
print DOT "\n}\n\n"; | |
close DOT; | |
system( "$DOT -T${format} -o ${PREFIX}-top.$format ${PREFIX}-top.txt" ); | |
my $i = 0; | |
foreach my $topDN (sort @L1Graph) { | |
$i++; | |
print "Writing ${PREFIX}-$i.txt...\n"; | |
open (DOT, ">${PREFIX}-$i.txt") or die "Could not open output file!"; | |
print DOT "digraph CT$i {\n"; # page=\"8.5,11\"\n margin=1\n"; | |
print DOT " rankdir=\"LR\";\n ratio=\"auto\";\n"; | |
traverse( $calltree{$topDN} ); | |
print DOT "}\n\n"; | |
close DOT; | |
print "Writing ${PREFIX}-$i.$format\n"; | |
system( "$DOT -T$format -o ${PREFIX}-$i.$format ${PREFIX}-$i.txt" ); | |
} | |
open (DOT, ">${PREFIX}-master.txt") or die "Could not open output file!"; | |
print DOT "digraph CallTree {\n"; # page=\"8.5,11\"\n margin=1\n"; | |
foreach my $acct (sort keys %user) { | |
my $entry = $calltree{$user{$acct}}; | |
displayNode( $entry ); | |
} | |
foreach my $acct (sort keys %user) { | |
my $entry = $calltree{$user{$acct}}; | |
if ($#{$entry} > 3) { | |
for my $i ( 4 .. $#{$entry} ) { | |
# Only add if subordinate is still an employee | |
if ($calltree{$entry->[$i]}) { | |
displayLink( $entry->[0], $calltree{$entry->[$i]} ); | |
} | |
} | |
} | |
} | |
print DOT "}\n\n"; | |
close DOT; | |
print "Writing ${PREFIX}-master.$format\n"; | |
system( "$DOT -T$format -o ${PREFIX}-master.$format ${PREFIX}-master.txt" ); | |
######################################################################################## | |
# Subroutines | |
######################################################################################## | |
sub traverse { | |
my $node = shift @_; | |
displayNode( $node ); | |
if ($#{$node} > 3) { | |
for $i ( 6 .. $#{$node} ) { | |
# Only add if mentee is still an employee | |
if ($calltree{$node->[$i]}) { | |
traverse( $calltree{$node->[$i]} ); | |
displayLink( $node->[0], $calltree{$node->[$i]} ); | |
} | |
} | |
} | |
} | |
sub displayNode { | |
my $node = shift @_; | |
print DOT "\t\"$node->[0]\" [label=\"$node->[1]\\n$node->[2]\\n$node->[4]\\n$node->[5]\""; | |
# Display this node differently if no telephoneNumber phone number | |
if ($node->[2] eq "UNKNOWN") { | |
print DOT ",shape=box,style=filled,color=\".7 .3 1.0\""; | |
} | |
print DOT "];\n"; | |
} | |
sub displayLink { | |
my $label = shift @_; | |
my $next = shift @_; | |
if (defined $next) { | |
print DOT "\t\"$label\" -> \"$next->[0]\"\n"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment