Our network team needed to true-up maintenance support, and validate inventory for our 6000+ Cisco IP Phones.
So I wrote a python script to provide the following information, via switch CDP discovery and dumping the phone's device details via http from the phones themselves:
- Switch, Port
- PhoneName
- IP
- Platform(From CDP)
- SerialNumber
- MACAddress
- ModelNumber
In a nutshell, the script performs the following:
- logs into each of the access switches provided (fed with file input, one per line)
- via CDP neighbor discovery grabs all devices named SEP* (standard Cisco convention)
- from that list, runs "cdp neighbor <int> detail" to get Platform and IP info for each discovered phone
- from the IP information, grabs the devices extended information via http (xml output)
- parses the xml and returns output as a csv file suitable for importing to a spreadsheet
#!/usr/bin/env python
"""
Mike Orstead
01/2013
http://stewpid-litterbox.blogspot.com/
Utility script to get Cisco IP phone device information through a combination
of Cisco switch info (CDP) and http grabs from the device themselves (xml).
Pre-reqs:
-switch access with a common username/password. User needs ability to run
cdp neighbor discover and "term len 0" (so command output is not run
through a pager)
-Update inputfile(s) below with site specific info (or modify to accept file location via user input)
NOTES and WARNINGS:-This script instance assumes that the CDP discover finds phones with
device names SEP*, script may be easily modified for other conventions.
-We have Cisco Video Conferencing devs that need to be handled, as IP address
information is not returned with CDP (CTS_CODEC). Other devices may need to
be handled similarly in your environment. Search reCODEC in the code below.
"""
# Import Python Modules
import os, datetime
import sys
import pexpect
import re
import getpass
import urllib
from lxml import etree
date = datetime.datetime.now()
datestamp = date.strftime("%Y%m%d")
def main():
source_choices = 'Run script against Site1 or Site2 switches? '
while True:
source_answer = raw_input(source_choices).lower()
if source_answer == 'site1':
source = 'Site1'
break
elif source_answer =='site2':
source = 'Site2'
break
else:
print "Invalid choice" + "\n"
userName = raw_input('Username: ')
userPassword = getpass.getpass('Password: ')
opspath = '/example-path/'
outputpath = opspath+'CIPphoneinfo_Output/'
# get the switches from proper inputfile
if source == 'Site1':
# TODO: should have a 'try' here
inputfile = open(opspath+'Site2_ACCESS_SWITCHES.txt')
ofile = 'cipinfo_Output_Site1.' + datestamp + '.csv'
outputfile = file(outputpath+ofile,'w')
elif source == 'Site2':
inputfile = open(opspath+'Site2_ACCESS_SWITCHES.txt')
ofile = 'cipinfo_Output_Site2.' + datestamp + '.csv'
outputfile = file(outputpath+ofile,'w')
output = {}
for hostname in inputfile:
phonelist = []
hostname = hostname.rstrip('\n')
logfile = sys.stdout
tmpf = os.tmpfile()
print 'Connecting to switch: ' + hostname + ' and discovering phones'
# TODO: should catch a TIMEOUT here in the pexpect
router = pexpect.spawn('ssh '+userName+'@'+hostname)
router.expect('word:')
router.sendline(userPassword)
router.expect('#')
router.sendline('term len 0')
router.expect('#')
# Gather device name / port
router.logfile = tmpf
router.sendline('show cdp neighbor | include SEP')
router.expect('#')
router.logfile = None
rePrompt = re.compile(hostname)
#output = {}
# iterate through the tmpfile, build the output dict entries
reEndLine = re.compile('\r\n')
reShowCDP = re.compile(r'show cdp neighbor*')
reCODEC = re.compile(r'CTS-CODEC')
tmpf.seek(0)
buf = tmpf.readlines()
for line in buf:
line = reEndLine.sub('', line)
# Skip these conditions from further discovery:
if re.match(reShowCDP,line): continue
if re.match(rePrompt,line): continue
if re.search(reCODEC,line): continue
output[line.split()[0]] = {
"switch": hostname,
"name": line.split()[0],
"port": line.split()[1] + " " + line.split()[2]
}
phonelist.append(line.split()[0])
tmpf.close()
i = 0
for x in output:
if output[x]['switch'] == hostname:
i += 1
numberphones = i
print "Found " + str(numberphones) + " phones on " + hostname
print "Grabbing phone details from switch " + hostname + ", and connecting to phones web interfaces for extended info"
for phone in phonelist:
tmpf2 = os.tmpfile()
router.logfile = tmpf2
router.sendline('show cdp neighbor '+output[phone]['port']+' detail')
router.expect("#")
router.logfile = None
reEndLine2 = re.compile('\r\n')
reShowCDP2 = re.compile(r'show cdp neighbor*')
rePrompt2 = re.compile("^.*#")
tmpf2.seek(0)
buf = tmpf2.readlines()
for line in buf:
line = reEndLine2.sub('', line)
if re.match(reShowCDP2,line): continue
if re.match(rePrompt2,line): continue
findIP = re.search('\s+IP address:\s+(\d+\.\d+\.\d+\.\d+)',line)
findPlat = re.search('Platform:\s+(.+),',line)
if findPlat:
output[phone]['platform'] = findPlat.group(1)
if findIP:
output[phone]['ipaddy'] = findIP.group(1)
# connect to http://<ipaddy>/DeviceInformationX to grab xml
url = 'http://' + output[phone]['ipaddy'] + '/DeviceInformationX'
try:
x = urllib.urlopen(url)
except IOError:
output[phone]['serialnum'] = "UNKNOWN - can't connect to " + url
output[phone]['modelnum'] = "UNKNOWN - can't connect to " + url
output[phone]['MAC'] = "UNKNOWN - can't connect to " + url
continue
try:
xmltree = etree.parse(x)
except:
# General Error catchall
output[phone]['serialnum'] = "UNKNOWN - can't parse xml"
output[phone]['modelnum'] = "UNKNOWN - can't parse xml"
output[phone]['MAC'] = "UNKNOWN - can't parse xml"
continue
else:
for ele in xmltree.iter("serialNumber"):
output[phone]['serialnum'] = ele.text
for ele in xmltree.iter("MACAddress"):
output[phone]['MAC'] = ele.text
for ele in xmltree.iter("modelNumber"):
output[phone]['modelnum'] = ele.text
tmpf2.close()
# outfile header row
outputfile.write("Switch,Port,PhoneName,IP,Platform(From CDP),SerialNumber,MACAddress,ModelNumber\n")
# Populate UNKNOWN where keys are missing.
for key in sorted(output.iterkeys()):
if not output[key].has_key('serialnum'): output[key]['serialnum'] = "UNKNOWN"
if not output[key].has_key('platform'): output[key]['platform'] = "UNKNOWN"
if not output[key].has_key('ipaddy'): output[key]['ipaddy'] = "UNKNOWN"
if not output[key].has_key('modelnum'): output[key]['modelnum'] = "UNKNOWN"
if not output[key].has_key('MAC'): output[key]['MAC'] = "UNKNOWN"
outputfile.write(output[key]['switch']+","+output[key]['port']+","+output[key]['name']+","+output[key]['ipaddy']+","+output[key]['platform']+","+output[key]['serialnum']+","+output[key]['MAC']+","+output[key]['modelnum']+"\n")
print "script complete - results can be found in: "+opspath+'CIPphoneinfo_Output/'+ofile
router.sendline ('exit')
sys.exit(0)
if __name__ == "__main__":
# We are running this module directly, rather than via import
main()
2 comments:
even though there are a lot of VoIP devices in the market today, cisco products are still on top and one of the most trusted brand
Csico is one of the top products as there are so many Voip hardware with lot more styles and different shapes, but still I love my cisco SPA 502g with great features and call forwarding.
Post a Comment