cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
13024
Views
30
Helpful
18
Replies

TCL Script to replace text in a file stored in flash

smolz
Level 4
Level 4

here is what i am trying to do.  I have a 2900 router with the ucs-e blade in it.  I am trying to automate the deployment of hundreds of routers with this setup.

 

So far I have gotten Cisco Prime Infrastructure up and running with the ability to push a new IOS to the router as well as a configuration built on a template.

As part of this I have an eem script that runs to clean up some configuration before sending the routers off to customer sites.  

I would like to have the eem script run the command "ucse 1 imc config save flash:imc.cfg" which would dump the cimc config to a text file.  Then i would like to be able to parse the file and replace some of the configuration items in there (hostname, dns servers, etc.) and then restore the CIMC config.  This way I wouldn't need to actually log into the CIMC to do this minor configuration step.

1 Accepted Solution

Accepted Solutions

Robert Radford
Level 1
Level 1

I have update the script and replied to the first item in the discussion as the flow of the discussion was getting a bit hard to follow.

The update includes some changes to allow the use of the path and filename variables throughout the script and provides some better explaination of the reason for each item.

I have also updated some of the variables so that there is not the potential for conflict and means it is easier to read if looking at this for the first time.

 

Good luck and I hope it helps.

 

Scrip below.

######################################################################################################
#
# Scrip to collect the configuration of CIMC configuration,
# modify the configuration, and write the modifications back to the flash file.
#
# Modified entries are for hostname and preferred DNS server. The new values of these entries are sent
# to the script when called.
#
# Certain procedures and methods have been implemented to overcome IOS specific issues in 15.2(4)M6a
# 2 key issues in this IOS are:
#   - the operation of the action_syslog command under tclsh, and
#   - the exec command "ucse 1 imc config save" causing the router to lockup when called from tclsh.
#
# These issues have been resolved in the script by:
#   1. writing to syslog direct, and
#   2. calling the exec command from config mode using the do prefix from within an applet.
#
# call the script "tclsh flash:cimc.tcl newhostname 8.8.8.8"
#
######################################################################################################


# Define variables
set configfile "cimc.cfg"
set path "flash"
set filecheck "$path:$configfile"

set HostName [lindex $argv 0]
set DnsServer [lindex $argv 1]

# Create procedure for sending to syslog to overcome potential issue with action_syslog in 15.2(4)M6a
proc SendSyslog { msg } {
    set syslog [open "syslog: " w+]
    puts $syslog "$msg"
    close $syslog
}

# Create procedure that writes to the file defined in the open path statement
proc WriteToFileProc {cmd} {
    global path
    set writetofile [open $path:eventapplet.txt w+]
    foreach a_cmd $cmd {
        puts $writetofile [set a_cmd]
    }
close $writetofile
}

# Define a procedure that when called will use the WriteFileProc to write the APPLET strings to the file
# doing this in a procedure removes potential buffer overrun if the applet is long and you are pasting directly into the tclsh
# This also allows for different applets to be constructed and called with a simple call.
#
# Note - when the applet contains a " you must prepend as \" so that it is written to the file
proc BuildApplet1Proc { } {
    global configfile
    global path    
    lappend  APPLET  "event manager applet CIMCSAVE"
    lappend  APPLET  "event none"
    lappend  APPLET  "action 1 cli command \"en\""
    lappend  APPLET  "action 2 cli command \"conf t\""
    lappend  APPLET  "action 3 cli command \"do ucse 1 imc config save $path:$configfile\""
    lappend  APPLET  "action 4 cli command \"end\""
    lappend  APPLET  "action 5 cli command \"exit\""
    lappend  APPLET  "!"
    lappend  APPLET  "end"
    WriteToFileProc $APPLET
}

# Validate that arguments have been passed to the script. This could be done with argc however
# we are using the content of the argv variables to validate
if { ($HostName == "") || ($DnsServer == "") } {
    SendSyslog "SCRIPT ERROR: Script called without arguments - exiting"
    SendSyslog "SCRIPT ERROR: usage = <scriptname> <hostname> <dnsip>"
    break
}

# Validate that the IP address of the DNS server is a valid IP construct
if {[regexp {^\d+\.\d+\.\d+\.\d+$} $DnsServer]
    && [scan $DnsServer %d.%d.%d.%d a b c d] == 4
    && 0 <= $a && $a <= 255 && 0 <= $b && $b <= 255
    && 0 <= $c && $c <= 255 && 0 <= $d && $d <= 255} {
} else {
    SendSyslog "SCRIPT ERROR: Script called with invalid DNS IP - exiting"
    SendSyslog "SCRIPT ERROR: usage = <scriptname> <hostname> <dnsip>"
    break
}

# Disable file prompting for file copy to running when loading the applet
ios_config "file prompt quiet"\
"end"

# If there is already a file on the flash - delete it first so that all instances are new when we use them.
SendSyslog "BUILDSCRIPT: Deleting any legacy files"
if { [file exists $filecheck] == 1} {  
    file delete -force $path:/$file
}
if { [file exists $path:eventapplet.txt] == 1} {  
    file delete -force $path:eventapplet.txt
}

# There is a problem is IOS for running the imc save command and hence we need to activate this via an applet from within tclsh
SendSyslog "BUILDSCRIPT: Building applet"
BuildApplet1Proc

# Copy the applet to the running config
SendSyslog "BUILDSCRIPT: Loading the applet into config"
exec "copy $path:eventapplet.txt running-config"

# Run a loop to check that the file has been created. This was created due to issues in 15.2(4)M6a where file creation was a problem
# The loop will run until proceed variable is set to a value other than 0
# the count variable is decremented each pass through the loop as to enable other functions to be called. These functions were put in place
# as under certain conditions, the cimc config could not be saved unless the UCSE module was powered.
# This may not be needed on other IOS - remove if required.
set proceed 0
set countdown 20
while {$proceed<=0} {
    after 3000
    SendSyslog "BUILDSCRIPT: Generating IMC config file"
    exec "event manager run CIMCSAVE"
    if { [file exists $filecheck] == 1} {  
        SendSyslog "BUILDSCRIPT: IMC Config file generated successfully - cleaning up"
        ios_config "no event manager applet CIMCSAVE"\
        "end"
        file delete -force $path:eventapplet.txt
        set proceed 1
    }
    if { $countdown == 15 } {
        # If countdown = 15 then open the UCSE interfaces
        ios_config "interface UCSE 1/0"\
        "no shutdown"\
        "end"
        ios_config "interface UCSE 1/1"\
        "no shutdown"\
        "end"
        puts "Configuring UCSE Interface"
    }
        # If countdown reaches 10 then start the UCSE
    if { $countdown == 10 } {
        exec "ucse 1 start"
        puts "starting UCSE"
    }
    set countdown [expr $countdown - 1]
}

# Read the config file setting the contents as a temp variable
SendSyslog "BUILDSCRIPT: Reading UCSE CIMC Config"
set configfile [exec "more $path:$configfile"]

# Replace the items in the variable "configfile" with the arguments supplied to the script
SendSyslog "BUILDSCRIPT: Replacing CIMC Config hostname with $HostName"
regsub -all -nocase {<hostname>([a-zA-Z0-9._-]*)} $configfile <hostname>$HostName configfile

SendSyslog "BUILDSCRIPT: Replacing CIMC Config DNS Address with $DnsServer"
regsub -all -nocase {<preferred-dns-server>([0-9.]*)} $configfile <preferred-dns-server>$DnsServer configfile

# Delete the original config file
SendSyslog "BUILDSCRIPT: Deleting Original UCSE CIMC config file"
file delete -force $path:/$configfile

# Create a new config file with the "configfile" information
SendSyslog "BUILDSCRIPT: Writing new UCSE CIMC config file"
set writefile [open "$path:$configfile" w+]
puts $writefile "$configfile\r"
close $writefile

# Send notification that the script has been completed
SendSyslog "BUILDSCRIPT: Configuration Complete !!"

 

View solution in original post

18 Replies 18

Robert Radford
Level 1
Level 1

Without knowing the specifics of what you want to change from and to, Its hard to give an operational example but the following should get you started for opening, modifying and writing files.

the regsub can be duplicated, put in a procedure, use regexp for variations etc... you can paste this into your tclsh console to test and then save as a file and call as required later.

 

for multiple replacement items you can duplicate the regsub line looking for new variables and replacing them as you go. not overly efficient but workable.

 

this script will delete the original on the flash prior to writing a new file so just be aware if doing some initial testing.


set file "imc.cfg"
set path "flash"

exec "ucse 1 imc config save $path:$file"

set configfile [exec "more $path:$file"]
regsub -all {\mWHATYOUWANTTOFIND\M} $configfile REPLACEITWITHTHIS configfile

file delete -force $path:/$file

set writefile [open "$path:$file" w+]
puts $writefile "$configfile\r"
close $writefile

So running into an issue with this line 

exec "ucse 1 imc config save $path:$file"

running this seems to lock up the console? It take the command and then nothing, it does seem to have run the command, it just doesn't come back to a prompt?  running the command manually has this for output:
Router#ucse 1 imc config save flash:cimc.cfg
IMC config saved
Router#
 
I had to restart the router to get it back.
 
Also running the rest of it doesn't seem to match either?
 
 

 

I am going to try and run that now.  What i am trying to replace is a line in the file like this "<preferred-dns-server>0.0.0.0</preferred-dns-server>"

With this "<preferred-dns-server>8.8.8.8</preferred-dns-server>"

what I would like to be able to match really is anything between the item, e.g. <preferred-dns-server>0.0.0.0</preferred-dns-server> or <preferred-dns-server>1.1.1.1</preferred-dns-server>

So either would match and be replaced.

 

 

once i have this working i can call this from a eem applet?

 

 

Wow... you are correct. I tried it here on a 3945 with UCS-E 160D and it locked up the router. I went through a heap of debugs and could not find out why. Here is the funny thing, if we step up and use ios_config with "do" it doesnt crash the router. this command under tcl exec appears to have problems.

Here is the workaround with variables removed so its easier to read. The find/replace will find any instance of dns-server>0.0.0.0 or dns-server>1.1.1.1 and replace with dns-server>8.8.8.8

this measn that both the lines for primary and alternate will be changed. you can modify this to change what you need

the command to generate the cimc.cfg file on the flash is taken from my router so modify the command for your ios version and environment, just include the "do" at the front otherwise it wont work.

 


ios_config "do ucse subslot 4/0 imc config save flash:cimc.cfg"

set configfile [exec "more flash:cimc.cfg"]
regsub -all {\mdns-server>(0.0.0.0|1.1.1.1)\M} $configfile dns-server>8.8.8.8 configfile

file delete -force flash:/cimc.cfg

set writefile [open "flash:cimc.cfg" w+]
puts $writefile "$configfile\r"
close $writefile

 

you dont need an applet to call this unless you want to schedule it at startup or on a trigger. If you just want to run it, save it to a tcl file on the flash and call it direct using for example

router# tclsh flash:cimccfg.tcl

If you want to validate that the changes have been made after the scrip is run, you can use the "more" command to view the file. eg:

router# more flash:cimc.cfg

 

Ok, so this is what i have so far and it seems to work:

 

ios_config "do ucse 1 imc config save flash:cimc.cfg"

set file "cimc.cfg"
set path "flash"
set configfile [exec "more $path:$file"]
regsub -all -nocase {<hostname>.*</hostname>} $configfile <hostname>NEWHOST</hostname> configfile
regsub -all -nocase {<preferred-dns-server>.*</preferred-dns-server>} $configfile <preferred-dns-server>8.8.8.8</preferred-dns-server> configfile
file delete -force $path:/$file
set writefile [open "$path:$file" w+]
puts $writefile "$configfile\r"
close $writefile

 

Now the only issue i have is to be able to create this as part of a deployment script in Prime Infrastructure?  This way I could use variables in the deployment and have this run from an eem applet.

so I am assuming that you want to pass the hostname and dns server ip addresses as configurable variables to the script so that you can customise this as you deploy.

Save the following as a tcl file on your flash and call it with the hostname and dns server ip that you want to change to

eg: tclsh flash:cimc.tcl newname 8.8.8.8

# Define variables
set file "cimc.cfg"
set path "flash"
set HostName [lindex $argv 0]
set DnsServer [lindex $argv 1]

# Validate that arguments have been passed to the script
if { ($HostName == "") || ($DnsServer == "") } {
    action_syslog msg "Script called without arguments - exiting"
    action_syslog msg "usage = <scriptname> <hostname> <dnsip>"
    break
}

# validate that the IP address of the DNS server is a valid IP construct
if {[regexp {^\d+\.\d+\.\d+\.\d+$} $DnsServer]
    && [scan $DnsServer %d.%d.%d.%d a b c d] == 4
    && 0 <= $a && $a <= 255 && 0 <= $b && $b <= 255
    && 0 <= $c && $c <= 255 && 0 <= $d && $d <= 255} {
} else {
    action_syslog msg "Script called with invalid DNS IP - exiting"
    action_syslog msg "usage = <scriptname> <hostname> <dnsip>"
    break
}

# Create the config file
ios_config "do ucse 1 imc config save flash:cimc.cfg"

# Read the config file setting the contents as a temp variable
set configfile [exec "more $path:$file"]

# replace the items in the variable "configfile" with the arguments supplied to the script
regsub -all -nocase {<hostname>([a-zA-Z0-9_-]*)} $configfile <hostname>$HostName configfile
regsub -all -nocase {<preferred-dns-server>([0-9.]*)} $configfile <preferred-dns-server>$DnsServer configfile

# Delete the original config file
file delete -force $path:/$file

# create a new config file with the "configfile" information
set writefile [open "$path:$file" w+]
puts $writefile "$configfile\r"
close $writefile


EDIT:

The reason I changed the regsub was to give you an example of matching explicit character sets. your previous regsub "<hostname>.*</hostname>" matches primarily on the <hostname>.* and the remainder is not used for a match. It will match but it is just extra as the .* will match against all characters after the <hostname>. Yes, both work but I thought I'd show you a different way in case you want to do some more explicit matching in the future.

I have also added some input validation to ensure that both arguments are provided to the script and that the IP address supplied for the DNS server is a valid (as in construct) IP address.

so here is what i have found so far.

If i run the command: tclsh flash:cimc.tcl newname 8.8.8.8

it runs and creates a file called cimc.cfg

When i try to look at that file, "more flash:cimc.cfg" i get:

RTR-1#more flash:cimc.cfg

%Error opening flash0:cimc.cfg (File not found)

When I run ''dir" i see that the file is tiny:

    6  -rw-          51   Aug 5 2014 16:41:40 -04:00  cimc.cfg

also, if i do not put both variables in when i run the command, e.g. tclsh flash:cimc.tcl newname i get this output:

RTR-1#tclsh flash:cimc.tcl newname        
invalid command name "action_syslog"
    while executing
"action_syslog msg "Script called without arguments - exiting""
    invoked from within
"if { ($HostName == "") || ($DnsServer == "") } {
    action_syslog msg "Script called without arguments - exiting"
    action_syslog msg "usage = <scr..."
    (file "flash:cimc.tcl" line 8)

 

Now if i open tclsh and paste everything in, (changing the hostname and dns variables in the definition) then it runs fine? 

 

 

 

What IOS are you running ?

System image file is "flash0:c2900-universalk9-mz.SPA.152-4.M6a.bin"

also, in trying to track down the issue, if i comment out everything past 

ios_config "do ucse 1 imc config save flash:cimc.cfg"

the file doesnt actually get written?  so it looks like the issue is with that command?

Ok,

There appear to be a couple of bugs in that release (from a testing perspective, not a bug ID perspective so you may have to log them.)

The first is that this IOS does not appear to honour the action_syslog command. I have created a workaround so that error does not appear and we dont have to use the action_syslog command.

The second is that the issuing of the ucse 1 imc config save command can operate differently from within different areas of the config. After a reboot, the initial issuing of this command fails under the CLI, the second iteration works successfully. The other issue we have with this command is the lock up of the router.

Here is the workaround that appears to be functional for this release with the ucse command - Create an applet that runs the ucse 1 imc command from within the config level of ios using the do prefix. run the applet from within the tclsh and then delete the applet if the creation of the config file was successful...... hmmm.....

I have also added syslog messages at different points in the script so you know where it is up to.

Here is the updated script contents.

# Define variables
set file "cimc.cfg"
set path "flash"
set filecheck "$path:$file"
set HostName [lindex $argv 0]
set DnsServer [lindex $argv 1]

# create procedure for sending to syslog to overcome potential issue with action_syslog in 15.2(4)M6a
proc SendSyslog { msg } {
    set syslog [open "syslog: " w+]
    puts $syslog "$msg"
    close $syslog
}

# Validate that arguments have been passed to the script
if { ($HostName == "") || ($DnsServer == "") } {
    SendSyslog "SCRIPT ERROR: Script called without arguments - exiting"
    SendSyslog "SCRIPT ERROR: usage = <scriptname> <hostname> <dnsip>"
    break
}

# validate that the IP address of the DNS server is a valid IP construct
if {[regexp {^\d+\.\d+\.\d+\.\d+$} $DnsServer]
    && [scan $DnsServer %d.%d.%d.%d a b c d] == 4
    && 0 <= $a && $a <= 255 && 0 <= $b && $b <= 255
    && 0 <= $c && $c <= 255 && 0 <= $d && $d <= 255} {
} else {
    SendSyslog "SCRIPT ERROR: Script called with invalid DNS IP - exiting"
    SendSyslog "SCRIPT ERROR: usage = <scriptname> <hostname> <dnsip>"
    break
}

# disable file prompting for file copy to running
ios_config "file prompt quiet"\
"end"

#If there is already a file on the flash - delete it first so that all instances are new when we use them.
SendSyslog "BUILDSCRIPT: Deleting any legacy files"
if { [file exists $filecheck] == 1} {  
    file delete -force $path:/$file
}

if { [file exists flash:eventapplet.txt] == 1} {  
    file delete -force flash:eventapplet.txt
}

#There is a problem is IOS for running the imc save command and hence we need to activate this via an applet from within tclsh
SendSyslog "BUILDSCRIPT: Building applet"
set file [open "flash:eventapplet.txt" w+]
puts $file {
event manager applet CIMCSAVE
 event none
 action 1 cli command "en"
 action 2 cli command "conf t"
 action 3 cli command "do ucse 1 imc config save flash:cimc.cfg"
 action 4 cli command "end"
 action 5 cli command "exit"
 !
 end
}
close $file

# copy the applet to the running config
SendSyslog "BUILDSCRIPT: Loading the applet into config"
exec "copy flash:eventapplet.txt running-config"

set proceed 0
set count 20
while {$proceed<=0} {
    after 3000
    SendSyslog "BUILDSCRIPT: Generating IMC config file"
    exec "event manager run CIMCSAVE"
    if { [file exists $filecheck] == 1} {  
        SendSyslog "BUILDSCRIPT: IMC Config file generated successfully - cleaning up"
        ios_config "no event manager applet CIMCSAVE"\
        "end"
        file delete -force flash:eventapplet.txt
        set proceed 1
    }
    if { $count == 15 } {
        ios_config "interface UCSE 1/0"\
        "no shutdown"\
        "end"
        ios_config "interface UCSE 1/1"\
        "no shutdown"\
        "switchport mode trunk"\
        "end"
        puts "Configuring UCSE Interface"
        }
        if { $count == 10 } {
        exec "ucse 1 start"
        puts "starting UCSE"
        }
    # puts "still doing"
    set count [expr $count - 1]
}.


# Read the config file setting the contents as a temp variable
SendSyslog "BUILDSCRIPT: Reading UCSE CIMC Config"
set configfile [exec "more $path:$file"]

# replace the items in the variable "configfile" with the arguments supplied to the script
SendSyslog "BUILDSCRIPT: Replacing CIMC Config hostname with $HostName"
regsub -all -nocase {<hostname>([a-zA-Z0-9_-]*)} $configfile <hostname>$HostName configfile
SendSyslog "BUILDSCRIPT: Replacing CIMC Config DNS Address with $DnsServer"
regsub -all -nocase {<preferred-dns-server>([0-9.]*)} $configfile <preferred-dns-server>$DnsServer configfile

# Delete the original config file
SendSyslog "BUILDSCRIPT: Deleting Original UCSE CIMC config file"
file delete -force $path:/$file

# create a new config file with the "configfile" information
SendSyslog "BUILDSCRIPT: Writing new UCSE CIMC config file"
set writefile [open "$path:$file" w+]
puts $writefile "$configfile\r"
close $writefile

SendSyslog "BUILDSCRIPT: Configuration Complete !!"

 

 

 

 

Is there a better version i should be running?

My apologies for the error in writing the file to flash. This was my fault as I had reused a variable in the script that caused the file to not get written. I was going to do some validation checking but this would only have made it longer and seeing as it should be fixed now.....

I have attached the scrip separately rather than paste the contents. Please test and let me know how you go. Notepad++ is a great text editor for windows if you are on that platform and it does highlighting of code for TCL (there are plenty out there, this is just one of them ).

The choice of IOS is a discussion that will generate many opinions. As you are running the Cisco referred IOS level for the UCSE as per the docco, I would stick with this for support reasons unless there is a feature in another code train that you require. Each train and release will have features, capabilities, and bugs and sometimes not all features will operate together on the one box. Testing during development is the key and even then you might not catch all the problems - this is why support through TAC is essential for your critical services.

Let me know how you go with this version - hopefully I got it right this time :)

 

 

so it seems that some of the script is running.

The last that i see in the log is:

000281: Aug  7 13:51:51.661 EDT: BUILDSCRIPT: Deleting any legacy files
000282: Aug  7 13:51:51.665 EDT: BUILDSCRIPT: Building applet
000283: Aug  7 13:51:52.165 EDT: BUILDSCRIPT: Loading the applet into config
000284: Aug  7 13:51:52.177 EDT: %PARSER-5-CFGLOG_LOGGEDCMD: User:unknown user  logged command:event manager applet CIMCSAVE
000285: Aug  7 13:51:52.177 EDT: %PARSER-5-CFGLOG_LOGGEDCMD: User:unknown user  logged command:event none 
000286: Aug  7 13:51:52.181 EDT: %PARSER-5-CFGLOG_LOGGEDCMD: User:unknown user  logged command:action 1 cli command "en"
000287: Aug  7 13:51:52.181 EDT: %PARSER-5-CFGLOG_LOGGEDCMD: User:unknown user  logged command:action 2 cli command "conf t"
000288: Aug  7 13:51:52.181 EDT: %PARSER-5-CFGLOG_LOGGEDCMD: User:unknown user  logged command:action 3 cli command "do ucse 1 imc config save flash:cimc.cfg"
000289: Aug  7 13:51:52.185 EDT: %PARSER-5-CFGLOG_LOGGEDCMD: User:unknown user  logged command:action 4 cli command "end"
000290: Aug  7 13:51:52.185 EDT: %PARSER-5-CFGLOG_LOGGEDCMD: User:unknown user  logged command:action 5 cli command "exit"
000291: Aug  7 13:51:52.189 EDT: %PARSER-5-CFGLOG_LOGGEDCMD: User:unknown user  logged command:!exec: copy *****

 

 

So it appears that it builds the 2nd script and copies it into the config (which i do see) and then nothing?

Getting Started

Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community:

Innovations in Cisco Full Stack Observability - A new webinar from Cisco