Using perl to automate ssh login using Net::SSH::Expect module.

This tutorial assumes you are using Red Hat or a Red Hat based distro (Centos, Fedora). The script itself will work on any operating system, but installing perl and modules steps may differ from OS to OS. We also assume that you have some very basic perl knowledge (because that is all I have) as teaching perl is beyond the scope of this article.

One good thing about being lazy (like me) is that you often learn interesting ways to accomplish tasks more efficiently.  I often have to log into servers to pull information, add a user, etc… These tasks are hard to script from a local machine because of the way SSH shell access works (creating a new shell instance when logging into remote server).  For a work around I used perl and a perl module called Net::SSH::Expect.

As far as I know there is no easier way to accomplish this.  There are ways to run a single command on a remote server using ssh and we will explore them further in a future article. If you want to run interactive commands, or you just feel more comfortable with perl, this is a fine way to get things done. I am sure there will be people out there who disagree and/or may know better ways, I would love to hear them all in the comments! So… Let’s get started….

Prerequisites

Your system must have perl installed.  Most modern linux distro’s come with perl pre-installed, but to check if you have perl run the following command as root (or using sudo) which should print out some information about your perl installation.  If it does not, you should look into how to install perl for your operating system.

perl -v

Example output:

This is perl 5, version 12, subversion 3 (v5.12.3) built for i386-linux-thread-multi
…truncated…

Now we need to install Net::SSH::Expect module from cpan.  If you are unfamiliar with how to install modules, please read "How to install perl modules with CPAN (perl-cpan)".

Coding

Now that we have all our prerequisites met, let’s get coding…. Open your favorite command line editor and get going...

For this first example we will write a simple script that will connect to a server, or servers of your choice and run a single command.

First we start with the shebang.

#!/usr/bin/perl

The above line most use the path to your perl binary file.  You can find this by typing “which perl” on the command line (without the quotes).

Now we tell perl to use the installed expect module like this:

# Net::SSH::Expect is used to connect via ssh and send commands to remote machine
use Net::SSH::Expect;

Now we will define the servers we want to connect to.  This is handy is you have to connect to multiple servers and run the same command.  For example if someone was to ask for a list of all user accounts on all your servers.  You can use this script to run “cat /etc/passwd” and retrieve a list of all the user accounts on multiple servers with one run of this script.  For this example will we pretend we have 5 servers, all named server1 through server5.

# Define servers array - List as many servers as you want, use the full FQDN or IP address
@servers = ("server1","server2","server3","server4","server5");

Let’s print the servers array so the user can easily see what servers he will be effecting.

foreach (@servers) {
        print "  $_ \n";
}

Next we will gather some information about how to connect to the servers.  We need the username and password the script should use to connect.  We do not want to store this information in the script, so we will ask for it each time the script is run. The username will be stored in the variable named $user.

print "Please enter your username for above systems? \n";
        $user = <>;
        chomp $user;

Next ask for the password, which is stored in the variable $pass.

        print "nPlease enter your password for the above systems? \n";
        $pass = <>;
        chomp $pass;

Next we will ask for the command the user wants to be run on all the servers, this is stored in the $command variable.

        print "nPlease enter the command you would like to run: \n";
        $command = <>;
        chomp $command;

One last piece of information that will make the script more effective is needed. Expect uses a timeout to wait for the expected output. Since some commands may take long to finish and produce output, we need to give expect an idea of how long it should wait for output.  Keep in mind if you make this timeout too long, expect will sit and wait for output.  So try to make this close to the expected time it will take.  For example running “cat /etc/passwd” would only take a second or less in most cases.  So you can make the timeout “3” as in three seconds. The expected timeout is stored in the $to variable.

        print "\n Estimated time command will take to run, for expect timeout: \n";
        $to = <>;
        chomp $to;

Now that we gathered all the information from the scripts user, let’s do some work with it.  We will create a foreach loop, which will loop through all the servers in the above array and run the command that was input by the user.

# Loop through the servers array and connect to one server (box) at a time.
foreach $box (@servers) {
# Print to screen what server you are connecting to
        print "Connecting to $box... \n";
        $ssh = Net::SSH::Expect->new (
                host => "$box",
                password => "$pass",
                user => "$user",
                raw_pty => 1
        );
        undef $login_output;
        eval {
                $login_output = $ssh->login(15);
        };
# If login output is empty try again
        while ($login_output eq "") {
                sleep(2);
                $login_output = $ssh->login(15);
        }
        if ($login_output =~ m/Last login/) {
                print "Login Successful... nn";
        } else {
                print "Login has failed! - Please check your username/password and caps lock.  nn";
                next;
        }

# RUN COMMANDS AS USER
                print "Running command.... \n";
                $ssh->send("$command");
                while ( defined ($output = $ssh->read_line($to)) ) {
# Send output of command to output array for printing when script is complete
                        push (@output, "$box: $output");
                        print $output . "n";
                }
}

Now that we have run the command and stored all the output in an array called @output, let’s print out all the output, each line will be started with the server name so we can see what output is from which machine.

print "\n Printing Report: \n";
foreach (@output) {
        print $_ . "n";
}