Welcome to the concluding article in this two-part series on using Expect (an extension to the Tcl language) to automate Cisco configuration tasks.

CCNA Training – Resources (Intense)

In the last article, we looked at how to install Expect on Windows OS and went on to write/edit a script to automate logging into several devices via Telnet and configuring SNMP on those devices. We concluded that article by discussing the need to handle errors in our script. Therefore in this article, we will be looking at how to catch and handle errors in Expect.

Ignoring case

The first thing we will consider is making our pattern matching case-insensitive. For example, when you remotely connect to some devices via Telnet or SSH, the “Username:” prompt may be in lowercase, as in “username:” This may also be due to the fact that the authentication prompts have been changed, such as when using the aaa authentication username-prompt or aaa authentication password-prompt commands for example.

By using the “-nocase” flag, we can instruct Expect to match the string without sensitivity to case. Therefore, “Username:” and “username:” will be the same to the script. Referring to our script in the last article, it means we can use the “-nocase” flag for the part of the script that deals with authentication as shown below:

expect –nocase "username:"
    send "$username\r"
    expect –nocase "password:"
    send "$password\r"

Script expecting a different string than the one displayed

Another common occurrence is that a different string is displayed rather than the string the script is expecting. We demonstrated this in the previous article as follows: in the network diagram below, we wanted to configure SNMP on R1 and R2. The script we wrote expects a username prompt followed by a password prompt before getting into global configuration mode.

What if R2 is configured only to require a password instead of a username and a password? The result as shown below is that the script encounters an error and aborts:

The cool thing is that the expect command can be told to expect different outputs and execute depending on which of them is received. For example, in our script, we can configure the expect command in such a way that if the username prompt is received, then everything goes as planned; however, if a password prompt is first received, we will leave that device and move to the next one. This can be done as follows:

expect {
    -nocase "username:" {
        send "$username\r"
    }
    -nocase "password:" {
        send_user "no username configured on $device\n"
        continue
    }
}

Notice how I have used curly brackets with the expect command. This section of the script basically says: “If you see the username prompt, send the configured username ($username); else, if you see the password prompt, send a message to the user that no username is configured on the device, then continue with the next device“. The send_user command will print a message on the user’s terminal while the continue command tells the script to continue with the next device in our “foreach” statement.

Let’s test our script now and see how it performs. Before proceeding, I will add another router, R3, connected to R2 with an IP address of 192.168.23.3. The entire script is then as follows:

#!/bin/sh
# \
exec tclsh "$0" ${1+"$@"}
package require Expect

# ###########################################################
# This simple Expect script will login into Cisco IOS devices
# and execute SNMP commands
# ###########################################################

# Define variables
set username "cisco"
set password "cisco"

# Define all the devices to be configured (separated by spaces)
set devices "192.168.56.100 192.168.12.2 192.168.23.3"

# Main loop
foreach device $devices {
	puts "Processing device: $device";
	
	# Open a telnet session to device. I use Plink.exe to telnet 
	# because I find that normal telnet may not work or in case
	# you don't have telnet enabled on your system. Plink is the
	# command line client of Putty. You can download plink from:
	# http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html 
	spawn plink -telnet $device

	# Perform authentication to login into device
	# My device configuration does not require enable password
	# so I will be placed in privilege EXEC mode (#) not USER mode (>)
	expect {
		-nocase "username:" {
			send "$username\r"
		}
		-nocase "password:" {
			send_user "no username configured on $device\n"
			continue
		}
}
			
	expect –nocase "password:"
	send "$password\r"
	expect "#"
	
	# Enter global configuration mode
	send "conf t\r"
	expect "(config)#"
	
	# Send SNMP configuration
	send "snmp-server community SNMP_COMM\r"
	expect "(config)#"
	send "snmp-server host 192.168.56.2 SNMP_COMM\r"
	expect "(config)#"
	
	# Return to privilege EXEC mode
	send "exit\r"
	expect "#"
	
	# Exit Telnet session
	send "exit\r"
	expect eof
				
}

Now when I run the script, notice what happens:

As you can see from the screenshot above, the script processed R1 successfully but when it got to R2, it printed out the error text we configured: “no username configured on 192.168.12.2” and then it moved on to the next device, which is R3. This way, we have added some level of resilience to our script which will save you a lot of time when configuring a lot of devices.

Unreachable device

Another scenario that happens often is that one of the devices in your list is unreachable. As of now, our script is not equipped to handle anything like this. For example, if I shut down R3, the script will try to reach it, fail, and then generate an error.

To solve this issue, we can use the “timeout” flag under our expect command. This will instruct the script that if the timeout value (default is 10 seconds) is exceeded, then the script should execute some action.

Hint: We can set the global timeout value using the set timeout command.

In our case, we will ask the script to print an error message and then continue to the next device (if any). To accomplish this, I will edit a part of our script as follows:

expect {
	-nocase "username:" {
		send "$username\r"
	}
	-nocase "password:" {
		send_user "no username configured on $device\n"
		continue
	}
	timeout {
		send_user “$device unreachable\n”
		continue
	}
}

Explanation: The script expects a username prompt and, if received, sends the configured username. Next, if the password prompt is received, it should send an error message and continue to the next device. Finally, if the timeout is exceeded, it will print an error message to the user and continue to the next device.

Let’s test our script to see how it behaves:

Great! It works just as expected. The thing about handling errors is that you will have to think of all the possible errors that may occur and define how you will handle them. For example, our script does not handle enable passwords: what if you encounter a device that asks for enable password? Think about how you will handle that. J

Summary

I hope you enjoyed this article which concludes our short series on using Expect language to write scripts that help us automate Cisco configuration tasks. In this article, we looked at three different possible errors that can occur and edited our script to handle those errors.

Resources, references and further reading

  1. div>Expect for Windows User Guide: http://docs.activestate.com/activetcl/8.4/expect4win/ex_usage.html