http://www.perlmonks.org?node_id=71921
Category: Networking Code
Author/Contact Info ybiC
Description: Automate collection of Cisco router and LAN switch information for inventory purposes:
    device name, hardware type, IOS/CatOS ver, serial number

From a Perlish standpoint, this has been an excercise in using hashes instead of buckets o' scalar variables or array with obtuse $file[0], and more discriminate use of subroutines so not need passel o' global variables.

Updated:
2001-04-13 perldoc switchver.pl for details.

Todo:
Use Net::Snmp sysDescr instead of Net::Telnet::Cisco.
    simplify data structure
    simplify parsing
    simplify program
    improve security

#!/usr/bin/perl -w

# switchver.pl
# pod at tail

use strict;
use Net::Telnet::Cisco;                    # simplify telnet to Cisco 
+devices
use Term::ReadKey;                         # disable screen echo durin
+g password entry
use Getopt::Long;                          # support command-line swit
+ches
use vars qw(
    $opt_target
    $opt_infile
    $opt_outfile
    @targets
    );

my %ntcparm = (
    errmode    => 'return',                # bad command won't kill sc
+ript
    ntctimeout => 20,                      # seconds to wait for devic
+e response
    pwtimeout  => 120,                     # seconds to wait for passw
+ord entry from user
    );
my %file = (
    def_out => 'switchver.csv',
    tmp0    => 'switchver.tmp0',
    tmp1    => 'switchver.tmp1',
    tmp2    => 'switchver.tmp2',
    );
my @tmpfiles = (
    $file{tmp0},
    $file{tmp1},
    $file{tmp2}
    );
my @commands = (                           # run these from Net::Telne
+t::Cisco session
    'term leng 0',                         # IOS
    'set leng 0',                          # CatOS
    'sho ver',                             # What This Program Is All 
+About
    );
my @keep = (                               # whole lines of text to be
+ *kept*
    '>sho ver',                            # IOS device name
    # 'Version',                           # IOS
    # 'System serial number',              # IOS
    # 'uptime',                            # IOS
    '> sho ver',                           # CatOS device name
    'WS-C',                                # CatOS
    );
my @strip = (                              # specific text to be *stri
+pped* from what's left
    '\r',                                  # CatOS bogus end-of-line (
+should be first regexp)
    '>sho ver',                            # IOS device name
    '> sho ver',                           # 2948g device name
    'System Bootstrap.*$',                 # 2948g
    'Hardware Version.*$',                 # 2948g
    'System serial number: ',              # IOS
    'Model number.*$',                     # 3548
    'cisco ',                              # IOS to leave model as lea
+ding string in line
    ' \(PowerPC.*$',                       # IOS processor, memory, et
+c following model number
    'Software.*$',                         # CatOS sw info following m
+odel number
    '^\d.*',                               # CatOS chassis blade hw in
+fo
    '^\s+',                                # leading blanks
    ',',                                   # first step to csv-ifying 
+output
    );

######################################################################
+############
# preliminaries and opening message
umask oct 177;                             # temp files viewable only 
+by user running this program
print "\nStarting $0\n";
GetOptions(
    'target=s'  => \$opt_target,
    'infile=s'  => \$opt_infile,
    'outfile=s' => \$opt_outfile,
    );

######################################################################
+############
# get list of target device(s)
if (defined $opt_target) {
    @targets = $opt_target;
    print "  Target given at command-line switch.\n";
    }
elsif (defined $opt_infile) {
    $file{in} = $opt_infile;
    unless (-r $file{in} && -T _) {
        &USAGE("Error reading input file \"$file{in}\": \"$!\"");
        }
    print "  Target list at $file{in}\n";
    open (INFILE, "< $file{in}")               or die "Error opening $
+file{in}: $!";
    @targets = <INFILE>;
    close INFILE;
    }
else {
    &USAGE('Error - target device or input file not specified!');
     }

######################################################################
+############
# get name of output file
if (defined $opt_outfile) {
    $file{out} = $opt_outfile;
    print "  Output file given at command-line switch.\n";
    }
else {
    $file{out} = $file{def_out};
    print "  Using default output file.\n";
    }

######################################################################
+########
# sanity-check target(s) list
print "  Validating target list...\n";
foreach my $nonsane(@targets) {
    chomp $nonsane;
    print ("    $nonsane\n");
    unless ($nonsane =~ (/^(\w|-|\.)+$/)) {
        print "\n  Ack - \"$nonsane\" is an improperly formatted devic
+e name.\n";
        print "  Only alphanumeric, underscore, dash and dot allowed.\
+n\n";
        print "  Edit $file{in} or re-enter targets to remove puctuati
+on, blank lines and/or blank spaces.\n\n";
        exit;                              # "exit" instead of "die" s
+o no error to console
        }
    }
print ("  Specified target name(s) valid.\n");

######################################################################
+############
# prompt for access password (enable pass not needed for 'show ver' co
+mmand
print "  Enter access password (*not* echoed to screen): ";
ReadMode('noecho');                        # don't echo password to sc
+reen
eval {
    local $SIG{ALRM} = sub { die "ALARUM" };
    alarm("$ntcparm{pwtimeout}");
    chomp($ntcparm{pwinput} = <STDIN>);
    alarm(0);
    };
if ($@ =~ /ALARUM/) {
    print "\n\n";
    print "Sorry - You waited too long before entering a password.\n";
    print "Try again, if you want.\n\n";
    ReadMode(0);                           # re-activate screen echo
    exit;                                  # "exit" instead of "die" s
+o no error to console
    }
$ntcparm{password} = $ntcparm{pwinput};
ReadMode(0);                               # re-activate screen echo
print "\n";

######################################################################
+########
# delete existing out/tmp files
print '  Unlinking prior out and temp files...';
&UNLINK($file{out}, 'quiet');
foreach my $tmpfile (@tmpfiles) {
    &UNLINK($tmpfile, 'quiet');
    }
print "  *poof*\n";

######################################################################
+############
# connect to each target device, then run command to get device info
print ("  Target devices queried:\n");
foreach my $target(@targets) {
    my $ntc;
    chomp $target;
    if ($ntc=Net::Telnet::Cisco->new(
        host      => $target,
        timeout   => $ntcparm{ntctimeout},
        errmode   => $ntcparm{errmode},
        input_log => $file{tmp0},
        ))
        {
        $ntc->login('',$ntcparm{password});
        print "    ", $ntc->last_prompt, " ";
        foreach my $command (@commands) {
            my @output = $ntc->cmd($command);
            }
        print "    ", $ntc->last_prompt, "\n";
        $ntc->close;

        # if multiple Cisco sessions, append each to same tmpfile
        open (TMP0, "<$file{tmp0}")        or die "Can't open $file{tm
+p0} RO: \"$!\"";
        open (TMP1, ">>$file{tmp1}")       or die "Can't open $file{tm
+p1} for append: \"$!\"";
        while (<TMP0>) { print TMP1 $_; }
        close (TMP0)                       or die "Can't close $file{t
+mp0}: \"$!\"";
        close (TMP1)                       or die "Can't close $file{t
+mp1}: \"$!\"";
        } else { warn "Can't connect to $target\n";} #: " . $ntc->errm
+sg; }          # if telnet connection failed
    my @output = $ntc->cmd();
    }
&UNLINK ($file{tmp0}, 'quiet');            # don't leave pw's laying a
+round

######################################################################
+############
# munge temp file data
# extract lines we *do* want to see
open (TMP1, "<$file{tmp1}")                or die "Can't open $file{tm
+p1} RO: \"$!\"";
open (TMP2, ">>$file{tmp2}")               or die "Can't open $file{tm
+p2} WO: \"$!\"";
while (<TMP1>) {
    foreach my $keep(@keep) {
        print (TMP2 $_) if /$keep/;
        }
    }
close (TMP1)                               or die "Can't close $file{t
+mp1}: \"$!\"";
close (TMP2)                               or die "Can't close $file{t
+mp2}: \"$!\"";
#---------------------------------------------------------------------
+---#
# delete specific text don't care about
open (TMP2, "<$file{tmp2}")                or die "Can't open $file{tm
+p2} RO: \"$!\"";
open (OUT,  ">>$file{out}")                or die "Can't open $file{ou
+t} WO: \"$!\"";
    while (<TMP2>) {
        foreach my $strip(@strip) {
            s/$strip//g;
            }
        s/\n/,\n/g;                        # second step toward csvify
+ing
        s/\s+,/,/g;                        # CatOS device-type
        chomp unless(/WS-/);               # final step to csvify
        print (OUT $_);
    }
close (TMP2)                               or die "Can't close $file{t
+mp2}: \"$!\"";
close (OUT)                                or die "Can't close $file{o
+ut}: \"$!\"";
 
######################################################################
+############
# turn out the lights, it's time to go
print "  Finished switch versions check.\n";
print '  Unlinking temp files...';
foreach my $tmpfile(@tmpfiles) {
    &UNLINK($tmpfile, 'quiet');
    }
print "  *poof*\n";
print "Results at $file{out}\n\n";


######################################################################
+########
# Subroutines start here
######################################################################
+########

# cleanup temp files when done with them
sub UNLINK {
    my $file = $_[0];
    my $echo = $_[1];
    if (-e $file && -w _) {
        print "  Unlinking $file..."       if ($echo eq 'verbose');
        unlink $file                       or die "Error unlinking $fi
+le: \"$!\"";
        print "  *poof*\n"                 if ($echo eq 'verbose');
        }
    }
######################################################################
+###
sub PAUSE {
    print "  Ctrl+c to abort  or  <enter> to continue.";
    (<STDIN>);
    }
######################################################################
+############
# Really don't have to explain this'un, eh?
sub USAGE {
    my $specific_err = $_[0];
    print <<EOF;

$specific_err

Usage: getopt.pl <(-t|--target) host> <(-i|--infile) file> <(-o|--outf
+ile) file>

If target given, then infile switch is ignored.
Target can be IP address or hostname or FQDN.

Switches include:
(note that "=" is optional)
  --target=sparky         *or*  -t 172.31.0.5
  --outfile another_file  *or*  -o=another_file
  --infile=some_file      *or*  -i some_file

You *must* specify either targets or infile.
If no outfile given, then hardcoded $file{def_out} is used.

EOF
    ;
    exit;
    }
######################################################################
+############


=head1 Name

 switchver.pl

=head1 Summary

 Automate collection of Cisco LAN switch and router information:
   device name, hardware type, IOS/CatOS ver, serial number
 Comments or suggestions are quite welcomed.

=head1 Usage

 Usage: getopt.pl <(-t|--target) host> <(-i|--infile) file> <(-o|--out
+file) file>

 If target given, then infile switch is ignored.
 Target can be IP address or hostname or FQDN.

 Switches include:
 (note that "=" is optional)
   --target=sparky         *or*  -t 172.31.0.5
   --outfile another_file  *or*  -o=another_file
   --infile=some_file      *or*  -i some_file

 You *must* specify either targets or infile.
 If no outfile given, then hardcoded $file{def_out} is used.


=head1 Tested

 with:    Perl 5.00503 on Debian 2.2 "Espy"
 against: 2916, 2924, 3548, 2948g, 4000 5000, 6000

=head1 Updated

 2001-04-13  17:30  Remove unecessary Getopt::Long switch abreviations
                    Outfile in csv format
                    Add '\r' to @strip regexp for CatOS data >8^(
 2001-04-11         Add command-line switches for target, infile, outf
+ile
                      using Getopt::Long.
                    Submit to www.perlmonks.org.
 2001-04-10         Hashamafy file and ntc variables.
                    Sanity-check target names.
                    Prompt for access password instead of hard-coded.
 2001-04-03         Eliminate unecessary global variables.
                    Consistant indenting.
                    Replace double quotes with single for strings.
                    Eliminate quotes for numbers.
                    Replace multiple "my $tmpfile" with @tmpfile.
                    Unlink temp files instead of overwrite.
                    Add &USAGE().
               
 2000-10-05         Initial working code.

=head1 ToDos

 Use Net::Snmp sysDescr instead of Net::Telnet::Cisco.
   simplify data structure
   simplify parsing/munging
   simplify program
   improve security
 Add regex so hostname with IPaddr-format not stripped by '^\d' in @st
+rip
 Fix ntc so not die on unreachable target
 Print console to logfile so record of failed attempts
 Hashamafy Getopt::Long scalar variables
 Add support for multiple targets and -t switch.
 Use File::Temp instead of $file{tmp(0..2)}.

=head1 Author

 ybiC

=head1 Credits

thanks to Petruchio, jeroenes, geektron, damian1301,
  crazyinsomniac, ar0n and yakko for Getopt::Long suggestions.

=cut
Replies are listed 'Best First'.
Re: Cisco sho ver
by idnopheq (Chaplain) on Apr 12, 2001 at 18:22 UTC
    Very nice! I like the interface, and this script provides useful information. I cannot count the number of routers & switches the LAN/WAN folks deploy w/o providing the info about the hardware. At least they're kind enough to set up the RADUIS and tell us the IP.

    Have yourself a Fresca(tm)!

    --
    idnopheq
    Apply yourself to new problems without preparation, develop confidence in your ability to to meet situations as they arrise.