Showing results for 
Search instead for 
Did you mean: 

Python script to retrieve hunt group member names


As a VoIP admin I often have site managers asking me for the names of agents in each of their hunt groups--some of which involve dozens of groups and hundreds of agent phones.  Since CUCM doesn't provide any direct way to get names from the hunt group (just DNs) these requests could take a couple of hours.  So I worked out this Python script that uses the Selenium module to export a spreadsheet of all hunt groups matching a given search term, with their respective members' names (based on DN "Description" field, as well as a directory of all the retrieved names' DNs.  This script requires you to install Firefox, and also the Firefox web driver from:

The web driver must be added to your system PATH per:


It's not the prettiest and I'm a pretty novice coder but it works and has turned an hour+ job into one that just takes a couple of minutes.  See comments on the right side for some substitutions you might need to make in the code based on your company's practices.  For instance in my company DNs are all 8 digits long, and the first 4 digits are always a common prefix specific to a given location.  E.g. one site may have directory numbers  30010000 - 30019999, another site may have 30020000 - 30029999, etc.  I have noted below where you would make adjustments based on your standards.  The Excel doc saves to whatever folder you have your Python projects in.


Feel free to ask questions or criticize!  (edited for readability)


#                       HUNT GROUP TOOL 3.0             

from selenium import webdriver
import openpyxl

browser = webdriver.Firefox()

browser.get('https://<IP of your CUCM>/ccmadmin') #open cucm
print("In the browser, sign into CUCM using your credentials.")
site = input("Enter site abbreviation: ").upper() # -> or, whatever search term works for your org.
print("Gathering " + site + " Hunt Groups...")
browser.get('https://<IP of your CUCM>/ccmadmin/') # go to line group page

lgelem = browser.find_element_by_id('searchString0') # locate search field
lgelem.send_keys(site) # enter site abbreviation to search
findbutton = browser.find_element_by_class_name('cuesButton') # find "Find" button and # click it

fullhuntdict = {} # dict of hunt groups. keys are LG names
# values are the lists of extensions
finalDict = {} # FINAL RESULT

huntgroupnames = browser.find_elements_by_partial_link_text(site) # list of returned LG names
prefixes = [] # list of DN prefixes
# for searching DNs
finalDNlist = []
namedirectory = []

def getDNs(extprefix): # get list of all DNs
browser.get('https://<IP of your CUCM>/ccmadmin/') # go to DN page

dnbuttonclear = browser.find_element_by_name('clearFilterButton') #clear previous search
dnelem = browser.find_element_by_id('searchString0') # locate search field
dnelem.send_keys(extprefix) # enter DN prefix to search
dnbuttonfind = browser.find_element_by_class_name('cuesButton') # find "Find" button and # click it
# below line updates the URL to allow for 500 results per page (change the 500 in URL if more needed):
'https://<IP of your CUCM>/ccmadmin/'

DNList = browser.find_elements_by_class_name('cuesTableRowEven') # list of even row elements
DNList2 = browser.find_elements_by_class_name('cuesTableRowOdd') # list of odd row elements
DNList += DNList2
fullDNlist = [] # all td elements from rows
siteDNlist = [] # final list just extensions & names

for i in range(len(DNList)): # this loop gets the text of each row
agent = DNList[i].find_elements_by_tag_name('td')
for j in range(len(agent)):

for i in fullDNlist: # strips it to just extensions & names
if i not in ['<partition name(s)>', '']: # <-phrase here should be partition name(s) on DN page


'''For loop to go through links in huntgroupnames'''
for i in range(len(huntgroupnames)):
huntgrouprefreshed = browser.find_elements_by_partial_link_text(site) # re-list LG names each iteration to
# avoid 'stale' error
choosegroup = huntgrouprefreshed[i] # click into selected group

'''now we're in the hunt group for this iteration'''
LGKey = browser.find_element_by_id('NAME') # find LG name field and
LGKey = LGKey.get_attribute("value") # store name for dict keys
huntmemberz = [] # clear agent list for each iteration
extensionlist = browser.find_elements_by_partial_link_text('<phrase>') # list of all DNs in LG
# <phrase> = search term matching DN partition
'''loop through all the values in extensionlist'''
for j in range(len(extensionlist)):
ext = extensionlist[j].text # retrieve link text for this iteration
ext = ext[:8] # your common DN length instead of 8
prefix = ext[:4] # your common DN prefix length instead of 4
huntmemberz.append(ext) # add ext to list
if prefix not in prefixes:
prefixes.append(prefix) # keep running collection of prefixes

fullhuntdict.update({LGKey: huntmemberz}) # add LG name and member list to dict


print("Gathering agent names. This may take a minute or two for larger sites.")
print("Do not click the back button or close your browser.")
for i in prefixes: # loop through collected prefixes
finalDNlist += getDNs(i) # add each group of DNs to final list

for lg in fullhuntdict.keys(): # loop through the stored hunt groups
finalhuntmembers = [] # reset this list before each group
for dn in range(len(fullhuntdict[lg])): # loop through DNs in current group
if fullhuntdict[lg][dn] in finalDNlist: # check if current DN in the DN list
addagent = finalDNlist.index(fullhuntdict[lg][dn]) # get index of current DN in DN list
addagent = finalDNlist[addagent + 1] # find next value in list (agent name)
if addagent not in finalhuntmembers: # check for redundancy and add agent
if addagent not in namedirectory: # add to cumulative directory
finalDict.update({lg: finalhuntmembers})

for key in finalDict.keys():
for member in finalDict[key]:

phonebook = {}
for i in range(len(finalDNlist)): # go through all of DN list
if finalDNlist[i][0] not in '0123456789': # if it's a name
if finalDNlist[i] not in phonebook.keys(): # check if already in phonebook
phonebook.update({finalDNlist[i]: [finalDNlist[i - 1]]}) # if not create dict item (name: list)
phonebook[finalDNlist[i]].append(finalDNlist[i - 1]) # otherwise add extension to existing
# name entry

for i in range(len(namedirectory)):
name = namedirectory[i]
entrylength = len(phonebook[name])
print(name, ' ' * (24 - len(name)), phonebook[name][entrylength - 1]) # sets up left-justified DNs


myWB = openpyxl.Workbook() # create workbook

sheet =
sheet.title = 'Groups' # name active sheet
myWB.create_sheet(title='DN Directory') # create second sheet

colnum = 1
columnlist = [] # list of columns needed for all groups
for i in letters:
columnlist.append(i) # add columns A - Z...
for i in letters:
columnlist.append('A' + i) # ...and AA - AZ to list

for key in finalDict.keys(): # loop through the hunt groups
rownum = 3 # start each list of names at row 3
sheet.cell(column=colnum, row=1).value = key # LG name in top row current column
for member in finalDict[key]: # loop through names in current LG
sheet.cell(column=colnum, row=rownum).value = member # add next name in list
rownum += 1 # go to next row
colnum += 1 # go to next column
for i in range(len(finalDict.keys())):
sheet.column_dimensions[columnlist[i]].width = 25 # widen enough columns for all LGs

sheet = myWB['DN Directory'] # switch to second sheet

dnrows = 1
for i in range(len(namedirectory)): # loop through names in directory
name = namedirectory[i]
phonebook[name].sort() # put current agent's DNs in order
entrylength = len(phonebook[name]) # no. of DNs for this agent
sheet.cell(column=1, row=dnrows).value = name # name in col. 1
sheet.cell(column=2, row=dnrows).value = phonebook[name][entrylength - 1] # last (highest) DN for agent in col. 2
dnrows += 1
sheet.column_dimensions['A'].width = 25
sheet.column_dimensions['B'].width = 20

saveit = input('Export to Excel? ')
if saveit.lower() in ['yes', 'y', 'ye']:
filename = input('Enter file name: ') + '.xlsx') #saves in local folder


Content for Community-Ad