Perl report from all Unix hosts using SSH

Posted: October 19, 2011 in solaris, tech

I always have Terminal running on my Macbook Pro at work. I use it to SSH in to numerous servers during the day to check things. You know, to administer the boxes. I have 59 Unix systems to take care of (some virtual but they still need administering). It won’t take long before you just get tired of typing your password and a command 59 times to check and see if all 59 boxes mount filerB or use DNS2.

If you are in a situation remotely like mine you’ve already setup your system to use Kerberos or SSH keys so you don’t have the type the password 59 times. But there’s still room for improvement. You’d have to print off a piece of paper listing all 59 servers and check them off one by one, or open up a spreadsheet and toggle back and forth between Terminal and Excel as you type the command and then type the answer.

Boring and tedious. So I wrote a little Perl script to do most of it for me. It’s not elegant, it’s just useful. Please not, I’m not really a programmer, just a hack. So if you see it and like it, use it! If you want to improve in it, go ahead– you can. I wouldn’t mind a peek at your changes.

Sure, I know some of you are going to recommend an App For That. Like Spiceworks. Or Cohesion. Go ahead– fire away. Let’s see what you recommend. We’ve tried a few and did not find one to fit our environment or budget yet. But I am interested in hearing about some options.

If you wan to try my script you will need to make a few edits and provide a few things beforehand.

First – you will need to be able to get into your servers from Terminal or an xterm without password prompts using SSH. It run as your user id executing the Perl script and use it to make the SSH connection. Don’t use root. Just don’t. This is a reporting tool, not an administration tool.

Second- you will need to provide a way for the system to get a list of servers. We maintain a text file on a NetApp qtree that is mountable as Cifs or NFS for our list of servers. It has more information in it that we do not need– such as, is it a global zone or a non-global zone? The critical part is that each line begins with the resolvable server name and the rest is discarded. In the script I have defined two locations of where the file can be found. You will find these definitions in scalar variables called location1 and location2. Why two? Because I want to be able to run this from my Mac or from a Unix system and the mountpaths are different. If it fails locating this file it will use a canned  array of hostnames (@hostlist), so you will want to edit that as well. It’s a fallback and guaranteed to be out of date in my environment. You can find these three things to edit in the subroutine MakeServerArray.

Third- the script uses two CPAN modules that you will need to provide your Perl where you run the script. MIME::Lite is the first and will handle the SMTP connection to send the report via email. The second is Term::ANSIColor and it will dress up the report to the Terminal window if that is the reporting method you choose.

Here’s my little script (this is going to look terrible in my current blog format):

# Run a command over SSH on lots of hosts and print a report on the results
# Simple html reports by email, or color to the screen
# Version 1, 9.19.2011, K.Creason

use MIME::Lite;
use Term::ANSIColor qw(:constants);
my $DEBUG=0;
my $cmd; my $title; my $email; my $report; my @hostlist;

# we require some arguments from the command line
if ("$ARGV[0]" eq "" ) { &help;}

# check for debug option
if ("$ARGV[0]" eq "-d")
{ $DEBUG=2; shift; print BOLD,YELLOW,ON_BLACK "Running in super-duper verbose debug mode:",RESET,"\n";}
# or if we have the not-quite debug verbose option
if ("$ARGV[0]" eq "-v")
{ $DEBUG=1; shift; }

# check to see if are going to email the report
if ("$ARGV[0]" eq "-e" )
if ($DEBUG gt 1){ print BOLD,YELLOW,ON_BLACK "\tThe report will be email to \"$email\"",RESET,"\n"; }

# check to see if the title of the report will be specified
if ("$ARGV[0]" eq "-t" )
if ($DEBUG gt 1){ print BOLD,YELLOW,ON_BLACK "\tThe report title is \"$title\"",RESET,"\n"; }

# and finally check for a command to run
if ("$ARGV[0]" eq "-c" )
if ($DEBUG gt 1){ print BOLD,YELLOW,ON_BLACK "\tWe have a command to run which is \"$cmd\"",RESET,"\n"; }

if ("$cmd" eq ""){ &help;}

if ("$title" eq ""){$title="Perl Report for \"$cmd\"."; }

# if an email address delivery is true we are doing HTML rather than color report to screen
if ("$email" ne "")
{ $report="<h2> $title </h2>\n<h5>PERL host report using command \"$cmd\"</h5>\n<table border=1>\n";}
else {print BLUE,BOLD "Report $title",RESET,BLUE "\nUsing command \"$cmd\"",RESET,"\n"; }

# find the servers to run report on

# now we are ready to go
my %host;
foreach $host (@hostlist)
# Here we run it over SSH and send STDERR to the bit bucket
chop($host{value}=`ssh $host "$cmd" 2>/dev/null`);
if ($DEBUG>1){ print YELLOW,ON_BLACK "\tDEBUG: command execute on $host returned \"$host{value}\".",RESET,"\n";}

# the next line is the easiest way to determine if the results were null, just a linefeed
if ($host{value} =~/12/) { $report=$report."<td><pre>$host{value}</pre></td></tr>\n";}
elsif ("$host{value}" eq ""){ $report=$report."<td>Unable to fulfill the request.</td></tr>\n";}
else { $report=$report."<td>$host{value}</td></tr>\n"; }
if ("$email" eq ""){ print "$host = > ",RED,"$host{value}",RESET,"\n"; }

if ("$email" ne "")
my $msg = MIME::Lite->new(
To =>"$email",
Subject =>"System Report: $title",
Type =>'multipart/related'
Type => 'text/html',
Data => qq{
else { print "\n\n"; }

# subs
sub MakeServerArray
my $location1="/Volumes/homeshare/systems/server.list";
my $location2="/share/systems/server.list";
my $discard; my $b;

if ( -f "$location1")
if ($DEBUG>0){print "Found a locally mounted server.list on a Mac system.\n\n";}
open SLIST, "<$location1";
foreach (<SLIST>){ if ( /^[a-zA-Z]/ ){ ($b,$discard)=(split( / /,$_,2)); push @hostlist,$b; } }
close SLIST;
elsif ( -f "$location2")
if ($DEBUG>0){print "Found a server.list on an NFS mounted share.\n\n";}
open SLIST, "<$location2";
foreach (<SLIST>){ if ( /^[a-zA-Z]/ ){ ($b,$discard)=(split( / /,$_,2)); push @hostlist,$b; } }
close SLIST;
print "\n\n\t!!!!!!\n\tUsing Canned Server List!! Unable to read the server.list in a known share location.\n!!!!\n\n";

@hostlist=qw(kona java arabica typica robusta bourbon duncan macbeth macduff agent86 agent99 hymie denali k2 everest bubba bubbajoe bubbabob michelangelo donatelo raphael leonardo splinter shredder);

foreach (@hostlist)
{ if ($DEBUG>0){print "DEBUG: processing server.list entry \"$_\"\n"; } }


sub help
die "\nUsage: $0 [-d or -v for optional debug OR verbose] [-e] -t \"title\" -c \"command to run\"
Use simple args-- if you have complex args and special or quoted
characters make a script to call with this utility.\n\n";


perl/  -t "Unix Resolv.conf settings" -c "cat /etc/resolv.conf"

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s