Looping through Interactive installer using expect - expect

I am trying to automate screen input of an interactive installer using expect and would like to use a loop which loops through lines of an answer file as input for part of the installer.
The installer has several sections to it, some of which are easily handled with simple expect/send responses. However there is a section in the installer which loops asking for data until you press a key to finish input. Then the rest of the installer continues.
The looping section looks like this on screen:
-------------------------------------------------------------------------------------------
Please enter ID details:
Please select an option: [A]dd [D]elete [I]mport [E]xport [F]inish =>A
Path: /opt/scanner/REF1/dump
ID: REF1
Please enter ID details:
Please select an option: [A]dd [D]elete [I]mport [E]xport [F]inish =>A
Path: /opt/scanner/REF2/dump
ID: REF2
Please select an option: [A]dd [D]elete [I]mport [E]xport [F]inish =>F
Rest of installer....
-------------------------------------------------------------------------------------------
Part of my expect script for the above looks like this:
expect -exact "Please select an option: \[A\]dd \[D\]elete \[I\]mport \[E\]xport \[F\]inish =>"
send -- "A\r"
expect -exact "Path: "
send -- "/opt/scanner/REF1/dump"
expect -exact "/opt/scanner/REF1/dump"
send -- "\r"
expect -exact "Please select an option: \[A\]dd \[D\]elete \[I\]mport \[E\]xport \[F\]inish =>"
send -- "A\r"
expect -exact "Path: "
send -- "/opt/scanner/REF2/dump"
expect -exact "/opt/scanner/REF2/dump"
send -- "\r"
expect -exact "Please select an option: \[A\]dd \[D\]elete \[I\]mport \[E\]xport \[F\]inish =>"
send -- "F\r"
Please excuse the verbosity as I have not been using expect long. This works but is not very efficient.
Ideally the answer file contains the REF1, REF2, until EOF (one per line) and a loop to read each line and send to the screen.
I have seen this solution but not sure how this fits in with the above.
I have been playing around with this Tcl code which may form a solution?
puts "/opt/scanner/$LINE/dump"
puts "$LINE"
Where $LINE is REF1, REF2 from the answer file.
Thanks in advance.

The first things to do to reduce the verbosity are: put the prompt into a variable; send the carriage return with the path.
set prompt "Please select an option: \[A\]dd \[D\]elete \[I\]mport \[E\]xport \[F\]inish =>"
expect -exact $prompt
send -- "A\r"
expect -exact "Path: "
send -- "/opt/scanner/REF1/dump\r"
expect -exact $prompt
send -- "A\r"
expect -exact "Path: "
send -- "/opt/scanner/REF2/dump\r"
send -- "\r"
expect -exact $prompt
send -- "F\r"
We could put this into a loop, but it adds some complexity
set refs [list "REF1" "REF2"]
set count 0
expect $prompt {
if {$count == 2} {
send -- "F\r"
} else {
send -- "A\r"
expect -exact "Path: "
set ref [lindex $refs $count]
send -- "/opt/scanner/$ref/dump\r"
incr count
exp_continue
}
}
The exp_continue command "loops" back to the "outer" expect command.

Related

How to use a while loop in Expect script for a certain prompt to appear?

I am using Expect language to automate a shell program that does the following:
There is a long AGREEMENT TEXT and the user should press SPACE for a few times until a prompt appears, which asks:
"Please type ACCEPT or EXIT:"
So if I want to emulate the user interaction in the Expect script it's like:
send -- " "
send -- " "
send -- " "
send -- " "
send -- " "
send -- " "
expect "*Please type ACCEPT or EXIT:*"
send -- "EXIT\r"
So what I want obviously is a way for the program to enter space until the prompt of "Please type..." appears on the screen, naturally, it seems like a while loop, what I want to do is: (pseudo-code):
while (expect "Please type ACCEPT or EXIT:" == -1) { //it doesn't appear
send -- " "
}
What is the correct syntax for this situation?

Writing Expect Script for random questions in while loop

I am trying to write an expect script with a while loop where I have a set of questions that will be asked randomly again and again. I have created a script for the same but that is not working as expected. Is it important that all the expectations in the expect block should be in the sequence? Also, Is it the correct way of exiting the while loop in case of success?
#!/usr/bin/expect -f
set timeout 1
spawn ./AnotherFile.sh
while {1} {
expect {
{Enter password:} {send -- "Password\r";exp_continue}
{Trust this certificate? [no]:} {send -- "yes\r";exp_continue}
{Enter pass phrase:} {send -- "Password\r";exp_continue}
{Verifying - Enter pass phrase:} {send -- "Password\r";exp_continue}
{Country Name (2 letter code) [AU]:} {send -- "IN\r";exp_continue}
{State or Province Name (full name) [Some-State]:} {send -- "XX\r";exp_continue}
{Locality Name (eg, city) []:} {send -- "XXXXX\r";exp_continue}
{Organization Name (eg, company) [Internet Widgits Pty Ltd]:} {send -- "XXXXX\r";exp_continue}
{Organizational Unit Name (eg, section) []:} {send -- "XXXXX\r";exp_continue}
{Common Name (e.g. server FQDN or YOUR name) []:} {send -- "XXXXX\r";exp_continue}
{Email Address []:} {send -- "\r";exp_continue}
}
expect {
{"Successfully Done."} {send -- "exit\r"}
}
}
expect eof
close $spawn_id
You can also think of my problem in terms of "C" code as:
While(1)
{
Switch(expect_command)
{
case "Enter Password":
send "Password\r"
case "is certificate valid"
send "yes\r"
...
case "successfull"
send "exit\r"
}
}
Put "Successfully Done" in the same expect command as the others. There should be at least one pattern that will not exp_continue so the expect command can actually end (without timing out).
Also, since you're in a while 1 loop, you need to send "exit\r"; break

expect script is not sending correct value

I wrote an expect script which will connect to a server and send a password depending on the prompt. The purpose is to update my password on new servers.
Basically, it will connect to a new server, expect to see (current) UNIX password: and then send my initial password. It will then see the 'New password:and 'Retype new password: prompts sending the new password each time.
My script loads these passwords from files into variables $pw1 and $pw2. I have evaluated them and verified that I have the correct values loaded into the variables. However, when I run the script, I am getting a token manipulation error on the initial password which tells me the value being sent is incorrect.
Perhaps my logic is incorrect?
EDIT: I have connected to one server that I'm running the script against and entered the old password exactly as it is in the file that the script loads into $pw2. It is working so I know that the password is not incorrect.
#! /usr/bin/expect --
#exp_internal 1
#set stty_init raw
set timeout 45
set prompt {\$ $}
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/user/bin/.pw1.txt]
set pw2 [exec cat /home/user/bin/.pw2.txt]
#puts $pw1
#puts $pw2
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho ssh -q $host
expect {
"*assword*" {
send -- "$pw1\r"
expect {
$prompt {
send -- exit\r
}
}
send -- exit\r
expect eof
}
-re $prompt {
send -- exit\r
expect eof
}
"(current)*" {
send -- "$pw2\r"
expect "New password"
send -- "$pw1\r"
expect "Retype new password"
send -- "$pw1\r"
puts \r
}
"continue*" {
send "yes\r"
expect {
"current" {
send -- "$pw2\r"
expect "New password"
send -- "$pw1\r"
expect "Retype new password"
send -- "$pw1\r"
puts \r
# expect eof
}
-re $prompt {
send -- exit\r
expect eof
}
}
}
}
}
puts \r
First, I'd like to note that you don't need to nest expect commands: look into the exp_continue command, instead. It will cut your code size in half simply by eliminating duplication.
Second, it looks your logic is, indeed, wrong. The first expect pattern is "*assword*". I would expect this to match when the remote server of your ssh is prompting for the user's current password, and it sends $pw1. However, everywhere else in your code, you seem to be treating $pw2 as the current password, and $pw1 as the new password.
Also, when you rewrite your code using exp_continue to eliminate the duplication and nesting, make sure you re-order your patterns so they're listed in order of most specific to least specific. Otherwise, you'll sometimes find the wrong pattern being matched, and the wrong code being executed.

Making decisions on expect return

I am trying to create an expect script which will send a different password string based on the "expect"
Condition A: If a cisco device has not been setup with a username then the first prompt will simply be "Password:" - then it should use passwordA (no username)
Condition B: If it has been setup with a username then the prompt will be "Username:" followed by "Password:" - then it should use Username and PasswordB
#!/bin/bash
# Declare host variable as the input variable
host=$1 
# Start the expect script
(expect -c "
set timeout 20
# Start the session with the input variable and the rest of the hostname
spawn telnet $host
set timeout 3
if {expect \"Password:\"} {
send \"PasswordA\"}
elseif { expect \"Username:\"}
send \"UsersName\r\"}
expect \"Password:\"
log_user 0
send \"PasswordB\r\"
log_user 1
expect \"*>\"
# send \"show version\r\"
# set results $expect_out(buffer)
#expect \"Password:\"
#send \"SomeEnablePassword\r\"
# Allow us to interact with the switch ourselves
# stop the expect script once the telnet session is closed
send \"quit\r\"
expect eof
")
You're doing it wrong. :)
The expect statement doesn't look to see what comes first, it waits until it sees what you ask for (and times out if it doesn't arrive in time), and then runs a command you pass to it. I think you can use it something like the way you're trying to, but it's not good.
expect can take a list of alternatives to look for, like a C switch statement, or a shell case statement, and that's what you need here.
I've not tested this, but what you want should look something like this:
expect {
-ex "Username:" {
send "UsersName\r"
expect -ex "Password:" {send "PasswordB\r"}
}
-ex "Password:" {send "PasswordA\r"}
}
In words, expect will look for either "Username:" or "Password:" (-ex means exact match, no regexp), whichever comes first, and run the command associated with it.
In response to the comments, I would try a second password like this (assuming that a successful login gives a '#' prompt):
expect {
-ex "Username:" {
send "UsersName\r"
expect -ex "Password:" {send "PasswordB\r"}
}
-ex "Password:" {
send "PasswordA1\r"
expect {
-ex "Password:" {send "PasswordA2\r"}
-ex "#" {}
}
}
}
You could do it without looking for the # prompt, but you'd have to rely on the second Password: expect timing-out, which isn't ideal.

Using conditional statements inside 'expect'

I need to automate logging into a TELNET session using expect, but I need to take care of multiple passwords for the same username.
Here's the flow I need to create:
Open TELNET session to an IP
Send user-name
Send password
Wrong password? Send the same user-name again, then a different password
Should have successfully logged-in at this point...
For what it's worth, here's what I've got so far:
#!/usr/bin/expect
spawn telnet 192.168.40.100
expect "login:"
send "spongebob\r"
expect "password:"
send "squarepants\r"
expect "login incorrect" {
expect "login:"
send "spongebob\r"
expect "password:"
send "rhombuspants\r"
}
expect "prompt\>" {
send_user "success!\r"
}
send "blah...blah...blah\r"
Needless to say this doesn't work, and nor does it look very pretty. From my adventures with Google expect seems to be something of a dark-art. Thanks in advance to anyone for assistance in the matter!
Have to recomment the Exploring Expect book for all expect programmers -- invaluable.
I've rewritten your code: (untested)
proc login {user pass} {
expect "login:"
send "$user\r"
expect "password:"
send "$pass\r"
}
set username spongebob
set passwords {squarepants rhombuspants}
set index 0
spawn telnet 192.168.40.100
login $username [lindex $passwords $index]
expect {
"login incorrect" {
send_user "failed with $username:[lindex $passwords $index]\n"
incr index
if {$index == [llength $passwords]} {
error "ran out of possible passwords"
}
login $username [lindex $passwords $index]
exp_continue
}
"prompt>"
}
send_user "success!\n"
# ...
exp_continue loops back to the beginning of the expect block -- it's like a "redo" statement.
Note that send_user ends with \n not \r
You don't have to escape the > character in your prompt: it's not special for Tcl.
With a bit of bashing I found a solution. Turns out that expect uses a TCL syntax that I'm not at all familiar with:
#!/usr/bin/expect
set pass(0) "squarepants"
set pass(1) "rhombuspants"
set pass(2) "trapezoidpants"
set count 0
set prompt "> "
spawn telnet 192.168.40.100
expect {
"$prompt" {
send_user "successfully logged in!\r"
}
"password:" {
send "$pass($count)\r"
exp_continue
}
"login incorrect" {
incr count
exp_continue
}
"username:" {
send "spongebob\r"
exp_continue
}
}
send "command1\r"
expect "$prompt"
send "command2\r"
expect "$prompt"
send "exit\r"
expect eof
exit
Hopefully this will be useful to others.
If you know the user ids and passwords, then you ought also to know which userid/password pairs are aligned with which systems. I think you'd be better off maintaining a map of which userid/password pair goes with which system then extracting that information and simply use the correct one.
So -- since you obviously don't like my advice, then I suggest you look at the wikipedia page and implement a procedure that returns 0 if successful and 1 if the expectation times out. That will allow you to detect when the password supplied failed -- the prompt expectation times out -- and retry. If this is helpful, you can remove your downvote now that I've edited it.
In retrospect, you'd probably want to do this in conjunction with the map anyway since you'd want to detect a failed login if the password was changed.

Resources