You are currently viewing CUCM AXL Python Programming – The next steps

CUCM AXL Python Programming – The next steps

Important Note: This post is very outdated now. I’d recommend highly against using suds-jurko and instead use Zeep as the SOAP Library for Python. I’ve updated my original AXL Programming post, however due to lack of spare time I haven’t had a chance to update this one.

The fundamentals of the post are perhaps still relevant but the code likely won’t even work due to API Changes in more recent versions of CUCM.

——————————

About one year ago I wrote a post  Getting Started with Python CUCM AXL API Programming, consolidating some of the information I’d gathered in using a popular SOAP library for Python “suds-jurko”. Since I’ve done quite a bit more over the last year with CUCM AXL and suds-jurko, I thought I’d write a follow up post to demonstrate a few more practical scenarios that might give people ideas for their own custom scripts / applications.

Here were a few ideas I had of some tasks that weren’t so easy, or weren’t possible at all just using BAT.

Update Directory Numbers

Here’s a topic that I’ve always found quite frustrating with CUCM and BAT, is that it’s not possible to actually update an existing directory number so easily. Most operations involve deleting and recreating the directory  number, which can have other unintended consequences, such as erasing Call Forwarding destinations or Display Names / Alerting Names.
For this example, it will be updating directory numbers (filtering for those starting with the number 5 – perhaps to indicate a particular direct inward dial range) into full +E.164 format. It’s becoming increasingly popular especially with larger global organisations to have all telephone numbers in +E.164 format at the Directory Number level, and to allow abbreviated dialing within the site simply configuring a locally significant translation pattern to expand and re-route to the full DN.
First we start off with the standard boilerplate code from my previous forum post. This builds the client object that will be used to run the various methods offered in the AXL WSDL.
from suds.client import Client
from suds.xsd.doctor import Import
from suds.xsd.doctor import ImportDoctor

wsdl = 'file:///C:/Development/axlsqltoolkit/schema/current/AXLAPI.wsdl'
location = 'https://cucmpub.lab.local:8443/axl/'
username = 'admin'
password = 'Cisco123'

tns = 'http://schemas.cisco.com/ast/soap/'
imp = Import('http://schemas.xmlsoap.org/soap/encoding/',
             'http://schemas.xmlsoap.org/soap/encoding/')
imp.filter.add(tns)

client = Client(wsdl,location=location,faults=False,plugins=[ImportDoctor(imp)],
                username=username,password=password)
Next, a bit of investigation into what methods we can use, and what the required input would be. For this a quick visit to Cisco DevNet AXL Schema (version 11.5 is https://developer.cisco.com/site/axl/documents/latest-version/axl-soap.gsp), and a search for the “listLine” method reveals that we need to give the searchCriteria, and the returnedTags we want.
Clicking on the searchCriteria shows us what attributes we can search for to limit the results.
Because I know that all the numbers corresponding to the DID range I’m using start with the number 5 and a partition of “dCloud_PT”, I’ll use both the pattern and routePartitionName attributes to filter.
Next, for the results, I also just want to return the pattern and routePartitionName, as I will use these in my script for the updateLine method. The returnedTags section shows what other options are available if you  needed to be more specific. You could also return more tags to allow you to further filter later in your script beyond what the searchCriteria offers.
Here is the result of the listLine method with my given filters:
resp = client.service.listLine(searchCriteria={'pattern' : '5%', 
             'routePartitionName' : 'dCloud_PT'}, 
             returnedTags={'pattern' : '', 'routePartitionName' : ''})
print resp

(200, (reply){
    return = 
       (return){
          line[] = 
             (LLine){
                _uuid = "{0BD5A715-60A9-BB1E-58F1-440ECE619E13}"
                pattern = "5211"
                routePartitionName = 
                   (routePartitionName){
                      value = "dCloud_PT"
                      _uuid = "{99C5A8FE-DD48-6E6A-CDEC-42262292EB98}"
                   }
             },
             (LLine){
                _uuid = "{A9D7208A-B9D6-8247-5B79-E0DAFD1500FF}"
                pattern = "5212"
                routePartitionName = 
                   (routePartitionName){
                      value = "dCloud_PT"
                      _uuid = "{99C5A8FE-DD48-6E6A-CDEC-42262292EB98}"
                   }
             },
             (LLine){
                _uuid = "{A8EA818F-0B86-995E-8D4B-31E078CE881E}"
                pattern = "5213"
                routePartitionName = 
                   (routePartitionName){
                      value = "dCloud_PT"
                      _uuid = "{99C5A8FE-DD48-6E6A-CDEC-42262292EB98}"
                   }
             },
       }
  })
The result is a tuple, with the first index being the return code (200 = OK) and the second part being the results. Next we’ll define a new variable that contains just the list of returned results that we can loop through in our script.
lines = resp[1]['return'].line
print lines

[(LLine){
   _uuid = "{0BD5A715-60A9-BB1E-58F1-440ECE619E13}"
   pattern = "5211"
   routePartitionName = 
      (routePartitionName){
         value = "dCloud_PT"
         _uuid = "{99C5A8FE-DD48-6E6A-CDEC-42262292EB98}"
      }
 }, (LLine){
   _uuid = "{A9D7208A-B9D6-8247-5B79-E0DAFD1500FF}"
   pattern = "5212"
   routePartitionName = 
      (routePartitionName){
         value = "dCloud_PT"
         _uuid = "{99C5A8FE-DD48-6E6A-CDEC-42262292EB98}"
      }
 }, (LLine){
   _uuid = "{A8EA818F-0B86-995E-8D4B-31E078CE881E}"
   pattern = "5213"
   routePartitionName = 
      (routePartitionName){
         value = "dCloud_PT"
         _uuid = "{99C5A8FE-DD48-6E6A-CDEC-42262292EB98}"
      }
 }]

for line in lines:
    print line.pattern, line.routePartitionName.value

5211 dCloud_PT
5212 dCloud_PT
5213 dCloud_PT
Now we’ll loop through all the lines in the list, and for each entry, update the existing directory number with the new directory number. I’ve added only very basic validation and no error handling, so this is something you might want to add yourself.
for line in lines:
    new_pattern = "+4930555" + line.pattern
    resp = client.service.updateLine(pattern=line.pattern, 
            routePartitionName=line.routePartitionName.value, 
            newPattern=new_pattern)
    
    if resp[0] == 200:
        print "Successfully Updated DN {} to {}".format(line.pattern, new_pattern)
    else:
        print "Error encountered updating DN {} to {}".format(line.pattern, new_pattern)

Successfully Updated DN 5211 to +49305555211
Successfully Updated DN 5212 to +49305555212
Successfully Updated DN 5213 to +49305555213
And the end result: all the directory numbers have had just their pattern updated, and all other attributes are the same.
Finally, all of the code consolidated for reference:

from suds.client import Client
from suds.xsd.doctor import Import
from suds.xsd.doctor import ImportDoctor

wsdl = 'file:///C:/Development/axlsqltoolkit/schema/current/AXLAPI.wsdl'
location = 'https://cucm1.dcloud.cisco.com:8443/axl/'
username = 'axl_admin'
password = 'dCloud12345!'

tns = 'http://schemas.cisco.com/ast/soap/'
imp = Import('http://schemas.xmlsoap.org/soap/encoding/',
             'http://schemas.xmlsoap.org/soap/encoding/')
imp.filter.add(tns)

client = Client(wsdl,location=location,faults=False,plugins=[ImportDoctor(imp)],
                username=username,password=password)

resp = client.service.listLine(searchCriteria={'pattern' : '5%', 
            'routePartitionName' : 'dCloud_PT'}, 
            returnedTags={'pattern' : '', 'routePartitionName' : ''})

for line in lines:
    new_pattern = "+4930555" + line.pattern
    resp = client.service.updateLine(pattern=line.pattern, 
            routePartitionName=line.routePartitionName.value, 
            newPattern=new_pattern)
    
    if resp[0] == 200:
        print "Successfully Updated DN {} to {}".format(line.pattern, new_pattern)
    else:
        print "Error encountered updating DN {} to {}".format(line.pattern, new_pattern)

This is just an example for changing the pattern itself, but in the AXL Schema documentation you can find many other attributes that you might want to modify (like CFW Calling Search Space, AAR Masks, External Phone Number Masks, etc.)

Update Telephone, Line and Associations

The next example will be a script that takes an existing Device and User as input, and performs a number of actions:

User:

  • Adds the Device as a “Controlled CTI Device”
  • Adds the user groups Standard CCM End User and Standard CTI Enabled
  • Updates the “Primary Directory Number” to the DN assigned to Line 1 of the Device

Device:

  • Updates the “Owner UserID” on the Device
  • Updates the Device Description with the first & last names of the user and the directory number

Line 1 on the Device:

  • Updates the Line Text Label (Note: this is actually the Line Appearance from the Device)
  • Updates the Line Description
  • Updates the Alerting Name
  • Updates the Display Name (Note: this is also the Line Appearance from the Device)

Firstly, let’s define the User and the Device we’re dealing with, then get the Device Details:

phone = 'SEP123456654320'
userid = 'john.doe'

get_phone_resp = client.service.getPhone(name='SEP123456654320')
print get_phone_resp

(200, (reply){
   return = 
      (return){
         phone = 
            (RPhone){
               _ctiid = 179
               _uuid = "{20DBA4D8-0055-A1CE-706D-7C7237021F21}"
               name = "SEP123456654320"
               description = "Tanya Adams - 8861 MRA - X6024"
               product = "Cisco 8861"
               model = "Cisco 8861"
               cls = "Phone"
               protocol = "SIP"
               protocolSide = "User"
               callingSearchSpaceName = 
                  (callingSearchSpaceName){
                     value = "dCloud_CSS"
                     _uuid = "{3A321D3D-0919-0C97-AF0C-F80E3CFB6695}"
                  }
               devicePoolName = 
                  (devicePoolName){
                     value = "dCloud_DP"
                     _uuid = "{1B1B9EB6-7803-11D3-BDF0-00108302EAD1}"
                  }
               commonDeviceConfigName = 
                  (commonDeviceConfigName){
                     value = "dCloud Common Device Configuration"
                     _uuid = "{9F0F7D5F-89A4-136C-20C0-90F3E54D9BCB}"
                  }
               commonPhoneConfigName = 
                  (commonPhoneConfigName){
                     value = "Standard Common Phone Profile"
                     _uuid = "{AC243D17-98B4-4118-8FEB-5FF2E1B781AC}"
                  }
               networkLocation = "Use System Default"
               locationName = 
                  (locationName){
                     value = "dCloud_Location"
                     _uuid = "{B59F63DC-F9FE-16CD-6C9B-9AB84F7AC572}"
                  }
               mediaResourceListName = 
                  (mediaResourceListName){
                     value = "dCloud_MRGL"
                     _uuid = "{2A76E338-7EF1-9B12-4BDE-E1DC7A04BE5C}"
                  }
               networkHoldMohAudioSourceId = "1"
               userHoldMohAudioSourceId = "1"
               automatedAlternateRoutingCssName = ""
               aarNeighborhoodName = ""
               loadInformation = 
                  (loadInformation){
                     value = "sip88xx.11-5-1-18"
                     _special = "false"
                  }
               vendorConfig = 
                  (XVendorConfig){
                     disableSpeaker[] = 
                        "false",
                     disableSpeakerAndHeadset[] = 
                        "false",
                     pcPort[] = 
                        "0",
                     voiceVlanAccess[] = 
                        "0",
                     webAccess[] = 
                        "0",
                     spanToPCPort[] = 
                        "1",
                     recordingTone[] = 
                        "0",
                     recordingToneLocalVolume[] = 
                        "100",
                     recordingToneRemoteVolume[] = 
                        "50",
                     powerPriority[] = 
                        "0",
                     minimumRingVolume[] = 
                        "0",
                     ehookEnable[] = 
                        "0",
                     headsetWidebandUIControl[] = 
                        "0",
                     headsetWidebandEnable[] = 
                        "0",
                     garp[] = 
                        "1",
                     allCallsOnPrimary[] = 
                        "0",
                     g722CodecSupport[] = 
                        "0",
                     webAdmin[] = 
                        "0",
                  }
               versionStamp = "{1453772260-43933CAB-193A-4C47-8A6F-773C5B65CCE1}"
               traceFlag = "false"
               mlppDomainId = ""
               mlppIndicationStatus = "Default"
               preemption = "Default"
               useTrustedRelayPoint = "Default"
               retryVideoCallAsAudio = "true"
               securityProfileName = 
                  (securityProfileName){
                     value = "Cisco 8861 - Standard SIP Non-Secure Profile"
                     _uuid = "{C7D7F171-28C8-4196-94A3-C9C5471DD5AA}"
                  }
               sipProfileName = 
                  (sipProfileName){
                     value = "dCloud Standard SIP Profile"
                     _uuid = "{7E2A3A02-A350-6A17-2967-BDF2998929E3}"
                  }
               cgpnTransformationCssName = ""
               useDevicePoolCgpnTransformCss = "true"
               geoLocationName = ""
               geoLocationFilterName = ""
               sendGeoLocation = "false"
               lines = 
                  (lines){
                     line[] = 
                        (RPhoneLine){
                           _uuid = "{8A34879B-4587-29EC-9788-249762960A89}"
                           index = "1"
                           label = "Tanya Adams - X6024"
                           display = "Tanya Adams - X6024"
                           dirn = 
                              (RDirn){
                                 _uuid = "{A7C50E64-6B3F-2C1F-611A-CCC24ED80D05}"
                                 pattern = "+19725556024"
                                 routePartitionName = 
                                    (routePartitionName){
                                       value = "dCloud_PT"
                                       _uuid = "{99C5A8FE-DD48-6E6A-CDEC-42262292EB98}"
                                    }
                              }
                           ringSetting = "Use System Default"
                           consecutiveRingSetting = "Use System Default"
                           ringSettingIdlePickupAlert = "Use System Default"
                           ringSettingActivePickupAlert = "Use System Default"
                           displayAscii = "Tanya Adams - X6024"
                           e164Mask = "+1972555XXXX"
                           dialPlanWizardId = ""
                           mwlPolicy = "Use System Policy"
                           maxNumCalls = "6"
                           busyTrigger = "2"
                           callInfoDisplay = 
                              (callInfoDisplay){
                                 callerName = "true"
                                 callerNumber = "false"
                                 redirectedNumber = "false"
                                 dialedNumber = "true"
                              }
                           recordingProfileName = 
                              (recordingProfileName){
                                 value = "MediaSense"
                                 _uuid = "32c8f8a7-1387-dc6e-5293-2c4b4572a4bb"
                              }
                           monitoringCssName = ""
                           recordingFlag = "Selective Call Recording Enabled"
                           audibleMwi = "Default"
                           speedDial = None
                           partitionUsage = "General"
                           associatedEndusers = 
                              (associatedEndusers){
                                 enduser[] = 
                                    (REnduserMember){
                                       userId = "tadams"
                                    },
                              }
                           missedCallLogging = "true"
                           recordingMediaSource = "Gateway Preferred"
                        },
                  }
               numberOfButtons = "10"
               phoneTemplateName = 
                  (phoneTemplateName){
                     value = "Standard 8861 SIP"
                     _uuid = "{3987DE44-582C-4E18-9691-7B07FBF30BCF}"
                  }
               speeddials = ""
               busyLampFields = ""
               primaryPhoneName = ""
               ringSettingIdleBlfAudibleAlert = "Default"
               ringSettingBusyBlfAudibleAlert = "Default"
               blfDirectedCallParks = ""
               addOnModules = ""
               userLocale = "English United States"
               networkLocale = "United States"
               idleTimeout = ""
               authenticationUrl = None
               directoryUrl = None
               idleUrl = None
               informationUrl = None
               messagesUrl = None
               proxyServerUrl = None
               servicesUrl = None
               services = ""
               softkeyTemplateName = ""
               loginUserId = ""
               defaultProfileName = ""
               enableExtensionMobility = "true"
               currentProfileName = ""
               loginTime = ""
               loginDuration = ""
               currentConfig = 
                  (currentConfig){
                     userHoldMohAudioSourceId = "1"
                     phoneTemplateName = 
                        (phoneTemplateName){
                           value = "Standard 8861 SIP"
                           _uuid = "{3987DE44-582C-4E18-9691-7B07FBF30BCF}"
                        }
                     mlppDomainId = ""
                     mlppIndicationStatus = "Default"
                     preemption = "Default"
                     softkeyTemplateName = ""
                     ignorePresentationIndicators = "false"
                     singleButtonBarge = "Off"
                     joinAcrossLines = "Off"
                     callInfoPrivacyStatus = "Default"
                     dndStatus = ""
                     dndRingSetting = ""
                     dndOption = "Use Common Phone Profile Setting"
                     alwaysUsePrimeLine = "Default"
                     alwaysUsePrimeLineForVoiceMessage = "Default"
                     emccCallingSearchSpaceName = 
                        (XFkType){
                           _uuid = ""
                        }
                     deviceName = ""
                     model = ""
                     product = ""
                     deviceProtocol = ""
                     cls = ""
                     addressMode = ""
                     allowAutoConfig = ""
                     remoteSrstOption = ""
                     remoteSrstIp = ""
                     remoteSrstPort = ""
                     remoteSipSrstIp = ""
                     remoteSipSrstPort = ""
                     geolocationInfo = ""
                     remoteLocationName = ""
                  }
               singleButtonBarge = "Off"
               joinAcrossLines = "Off"
               builtInBridgeStatus = "On"
               callInfoPrivacyStatus = "Default"
               hlogStatus = "On"
               ownerUserName = 
                  (ownerUserName){
                     value = "tadams"
                     _uuid = "61a1d409-6ff5-449d-5988-33f1722888b9"
                  }
               ignorePresentationIndicators = "false"
               packetCaptureMode = "None"
               packetCaptureDuration = "0"
               subscribeCallingSearchSpaceName = ""
               rerouteCallingSearchSpaceName = ""
               allowCtiControlFlag = "true"
               presenceGroupName = 
                  (presenceGroupName){
                     value = "Standard Presence group"
                     _uuid = "{AD243D17-98B4-4118-8FEB-5FF2E1B781AC}"
                  }
               unattendedPort = "false"
               requireDtmfReception = "false"
               rfc2833Disabled = "false"
               certificateOperation = "No Pending Operation"
               certificateStatus = "None"
               upgradeFinishTime = None
               deviceMobilityMode = "Default"
               remoteDevice = "false"
               dndOption = "Use Common Phone Profile Setting"
               dndRingSetting = ""
               dndStatus = "false"
               isActive = "true"
               isDualMode = "false"
               mobilityUserIdName = ""
               phoneSuite = "Default"
               phoneServiceDisplay = "Default"
               isProtected = "false"
               mtpRequired = "false"
               mtpPreferedCodec = "711ulaw"
               dialRulesName = ""
               sshUserId = ""
               digestUser = ""
               outboundCallRollover = "No Rollover"
               hotlineDevice = "false"
               secureInformationUrl = ""
               secureDirectoryUrl = ""
               secureMessageUrl = ""
               secureServicesUrl = ""
               secureAuthenticationUrl = ""
               secureIdleUrl = ""
               alwaysUsePrimeLine = "Default"
               alwaysUsePrimeLineForVoiceMessage = "Default"
               featureControlPolicy = ""
               deviceTrustMode = "Not Trusted"
               confidentialAccess = 
                  (confidentialAccess){
                     confidentialAccessMode = ""
                     confidentialAccessLevel = "-1"
                  }
               requireOffPremiseLocation = "false"
               cgpnIngressDN = ""
               useDevicePoolCgpnIngressDN = "true"
               msisdn = ""
               enableCallRoutingToRdWhenNoneIsActive = "false"
               wifiHotspotProfile = ""
               wirelessLanProfileGroup = ""
            }
      }
 })

Next we can update the user, to add the CTI Device Association, the user groups, and update the Primary Directory Number to that of the associated Device.

# Define the list of associated devices. 
# This is a list of 0 or more SEPXXXXXXXX tags
phone = resp[1]['return'].phone.name
associated_devices = [{'device' : resp[1]['return'].phone.name}]

# Define the Groups to be added to the user. 
# Under "userGroup" is a list of GroupName tags.
associated_groups = {'userGroup' : [{'name' : 'Standard CCM End Users'}, 
                                    {'name' : 'Standard CTI Enabled'}]}

# Get the DN Pattern and Partition of the first line from the getPhone query

dn_pattern = resp[1]['return'].phone.lines.line[0].dirn.pattern
dn_partition = resp[1]['return'].phone.lines.line[0].dirn.routePartitionName.value

# Update the user
update_user_resp = client.service.updateUser(userid=userid, 
                      associatedDevices=associated_devices, 
                      associatedGroups=associated_groups, 
                      primaryExtension={'pattern' : dn_pattern, 
                                        'routePartitionName' : dn_partition})

print update_user_resp

(200, (reply){
   return = "{86EFA455-4753-0DF4-2501-477E8EAC68FE}"
 })

Next we need to gather the details of the user to be able to update the Device / Line.

# Get the User Details
get_user_resp = client.service.getUser(userid=userid)

print get_user_resp

(200, (reply){
   return = 
      (return){
         user = 
            (RUser){
               _uuid = "{86EFA455-4753-0DF4-2501-477E8EAC68FE}"
               firstName = "John"
               middleName = None
               lastName = "Doe"
               userid = "john.doe"
               password = None
               pin = None
               mailid = "john.doe@cisco.com"
               department = None
               manager = None
               userLocale = ""
               associatedDevices = 
                  (associatedDevices){
                     device[] = 
                        "SEP123456654320",
                  }
               primaryExtension = 
                  (primaryExtension){
                     pattern = "+19725556024"
                     routePartitionName = "dCloud_PT"
                  }
               associatedPc = None
               associatedGroups = 
                  (associatedGroups){
                     userGroup[] = 
                        (userGroup){
                           name = "Standard CCM End Users"
                           userRoles = 
                              (userRoles){
                                 userRole[] = 
                                    "Standard CCM End Users",
                                    "Standard CCMUSER Administration",
                              }
                        },
                        (userGroup){
                           name = "Standard CTI Enabled"
                           userRoles = 
                              (userRoles){
                                 userRole[] = 
                                    "Standard CTI Enabled",
                              }
                        },
                  }
               enableCti = "true"
               digestCredentials = None
               phoneProfiles = ""
               defaultProfile = ""
               presenceGroupName = 
                  (presenceGroupName){
                     value = "Standard Presence group"
                     _uuid = "{AD243D17-98B4-4118-8FEB-5FF2E1B781AC}"
                  }
               subscribeCallingSearchSpaceName = ""
               enableMobility = "false"
               enableMobileVoiceAccess = "false"
               maxDeskPickupWaitTime = "10000"
               remoteDestinationLimit = "4"
               associatedRemoteDestinationProfiles = ""
               passwordCredentials = 
                  (passwordCredentials){
                     pwdCredPolicyName = "dCloud Credential Policy"
                     pwdCredUserCantChange = "false"
                     pwdCredUserMustChange = "false"
                     pwdCredDoesNotExpire = "true"
                     pwdCredTimeChanged = "May 27, 2017 08:14:28 CDT"
                     pwdCredTimeAdminLockout = None
                     pwdCredLockedByAdministrator = "false"
                  }
               pinCredentials = 
                  (pinCredentials){
                     pinCredPolicyName = "dCloud Credential Policy"
                     pinCredUserCantChange = "false"
                     pinCredUserMustChange = "false"
                     pinCredDoesNotExpire = "true"
                     pinCredTimeChanged = "May 27, 2017 08:14:27 CDT"
                     pinCredTimeAdminLockout = None
                     pinCredLockedByAdministrator = "false"
                  }
               associatedTodAccess = ""
               status = "1"
               enableEmcc = "false"
               associatedCapfProfiles = ""
               ctiControlledDeviceProfiles = ""
               patternPrecedence = ""
               numericUserId = None
               mlppPassword = None
               customUserFields = ""
               homeCluster = "true"
               imAndPresenceEnable = "false"
               serviceProfile = 
                  (serviceProfile){
                     value = "dCloud_Service-Profile"
                     _uuid = "{4B5DE118-6185-A12B-6164-EF523DE23753}"
                  }
               lineAppearanceAssociationForPresences = 
                  (lineAppearanceAssociationForPresences){
                     lineAppearanceAssociationForPresence[] = 
                        (RLineAppearanceAssociationForPresence){
                           _uuid = "{8A34879B-4587-29EC-9788-249762960A89}"
                           laapAssociate = "t"
                           laapProductType = "Cisco 8861"
                           laapDeviceName = "SEP123456654320"
                           laapDirectory = "+19725556024"
                           laapPartition = "dCloud_PT"
                           laapDescription = "Tanya Adams - 8861 MRA - X6024"
                        },
                  }
               directoryUri = "john.doe@cisco.com"
               telephoneNumber = None
               title = None
               mobileNumber = None
               homeNumber = None
               pagerNumber = None
               extensionsInfo = 
                  (extensionsInfo){
                     extension[] = 
                        (RExtension){
                           _uuid = "{840FB340-0348-3BD2-55CA-B4704987DF48}"
                           sortOrder = "0"
                           pattern = 
                              (pattern){
                                 value = "+19725556024"
                                 _uuid = "{A7C50E64-6B3F-2C1F-611A-CCC24ED80D05}"
                              }
                           routePartition = "dCloud_PT"
                           linePrimaryUri = "tadams@sip.dcloud.cisco.com"
                           partition = 
                              (partition){
                                 value = "dCloud_PT"
                                 _uuid = "{99C5A8FE-DD48-6E6A-CDEC-42262292EB98}"
                              }
                        },
                  }
               selfService = "19725556024"
               userProfile = ""
               calendarPresence = "false"
               ldapDirectoryName = ""
               userIdentity = None
               nameDialing = "DoeJohn"
               ipccExtension = ""
               convertUserAccount = ""
            }
      }
 })

Here, using the attributes of the user, we can update the Device Description

# Gather the attributes needed to update the Telephone and Line
first_name = get_user_resp[1]['return'].user.firstName
last_name = get_user_resp[1]['return'].user.lastName

# Define Description in the format "FirstName LastName - DirectoryNumber"
# Because the DN in this case is +E.164 with a 
# we have to skip the  otherwise it's not in a valid format.
description = "{} {} - {}".format(first_name, last_name, dn_pattern[1:])

# First, get the line appearance from the getDevice query initiated earlier
line = get_phone_resp[1]['return'].phone.lines.line[0]

# Next define the updated line appearance variables
# For the Label, we'll use "LastName - 5-Digit-Ext"
line.label = last_name + " - " + dn_pattern[-5:]

# For the Display, it'll be "FirstName LastName"
line.display = first_name + " " + last_name 
line.displayAscii = first_name + " " + last_name 


# Update the Phone with a Description consisting of the First Name,
# Last Name and Directory Number
update_phone_resp = client.service.updatePhone(name=phone, 
               description=description, 
               ownerUserName=userid, 
               lines={'line' : line})

print update_phone_resp

(200, (reply){
   return = "{20DBA4D8-0055-A1CE-706D-7C7237021F21}"
 })

And then the Line Description, Alerting Name, Display Name, Line Text Label, and Line Association.

# Next update the Line itself for non-line appearance settings such as the Alerting Name and Description

update_line_resp = client.service.updateLine(pattern=dn_pattern, 
               routePartitionName=dn_partition, 
               description=description, 
               alertingName=first_name + " " + last_name, 
               asciiAlertingName=first_name + " " + last_name)

print update_line_resp

(200, (reply){
   return = "{A7C50E64-6B3F-2C1F-611A-CCC24ED80D05}"
 })

And there you have it. Here is the summarised script:


from suds.client import Client
from suds.xsd.doctor import Import
from suds.xsd.doctor import ImportDoctor

wsdl = 'file:///C:/Development/axlsqltoolkit/schema/current/AXLAPI.wsdl'
location = 'https://cucm1.dcloud.cisco.com:8443/axl/'
username = 'axl_admin'
password = 'dCloud12345!'

tns = 'http://schemas.cisco.com/ast/soap/'
imp = Import('http://schemas.xmlsoap.org/soap/encoding/',
             'http://schemas.xmlsoap.org/soap/encoding/')
imp.filter.add(tns)

client = Client(wsdl,location=location,faults=False,plugins=[ImportDoctor(imp)],
                username=username,password=password)

# Specify the Phone Name and UserID to associate with one another.
# You could also have these input as arguments at the command line rather than static in the script.
phone = 'SEP123456654320'
userid = 'john.doe'

# Get the Phone
get_phone_resp = client.service.getPhone(name=phone)

# Define the list of associated devices for the user.
# This is a list of 0 or more SEPXXXXXXXX tags
phone = get_phone_resp[1]['return'].phone.name
associated_devices = [{'device' : phone}]

# Define the Groups to be added to the user. 
# Under "userGroup" is a list of GroupName tags.
associated_groups = {'userGroup' : [{'name' : 'Standard CCM End Users'}, 
                                    {'name' : 'Standard CTI Enabled'}]}

# Get the DN Pattern and Partition of the first line from the getPhone query

dn_pattern = get_phone_resp[1]['return'].phone.lines.line[0].dirn.pattern
dn_partition = get_phone_resp[1]['return'].phone.lines.line[0].dirn.routePartitionName.value

# Update the user
update_user_resp = client.service.updateUser(userid=userid, 
                      associatedDevices=associated_devices, 
                      associatedGroups=associated_groups, 
                      primaryExtension={'pattern' : dn_pattern, 
                                        'routePartitionName' : dn_partition})

# Get the User Details
get_user_resp = client.service.getUser(userid=userid)

# Gather the attributes needed to update the Telephone and Line
first_name = get_user_resp[1]['return'].user.firstName
last_name = get_user_resp[1]['return'].user.lastName

# Define Description in the format "FirstName LastName - DirectoryNumber"
# Because the DN in this case is +E.164 with a 
# we have to skip the  otherwise it's not in a valid format.
description = "{} {} - {}".format(first_name, last_name, dn_pattern[1:])

# First, get the line appearance from the getDevice query initiated earlier
line = get_phone_resp[1]['return'].phone.lines.line[0]

# Next define the updated line appearance variables
# For the Label, we'll use "LastName - 5-Digit-Ext"
line.label = last_name + " - " + dn_pattern[-5:]

# For the Display, it'll be "FirstName LastName"
line.display = first_name + " " + last_name 
line.displayAscii = first_name + " " + last_name 


# Update the Phone with a Description consisting of the First Name, 
# Last Name and Directory Number
update_phone_resp = client.service.updatePhone(name=phone, 
                     description=description, 
                     ownerUserName=userid, 
                     lines={'line' : line})
print update_phone_resp

# Next update the Line itself for non-line appearance settings
# such as the Alerting Name and Description
update_line_resp = client.service.updateLine(pattern=dn_pattern, 
                      routePartitionName=dn_partition, 
                      description=description, 
                      alertingName=first_name + " " + last_name, 
                      asciiAlertingName=first_name + " " + last_name)

Wrap-Up / Tips

So, there are a couple of other examples of how to use Python with suds-jurko. I’ll leave this post with a few tips:
    • If you’re getting syntax errors, check the CUCM AXL Schema pages and try and get a better understanding of what inputs it requires for updates / adds. Sometimes it’s a little more convoluted than you think it will be. A good example was the “associatedGroups” attribute in the updateUser method. There I find it helps to think of how suds will take your dict data and transform it into XML.For example:
      <associatedGroups>
      <userGroup>
      <name>Standard CCM End Users</name>
      </userGroup>
      <userGroup>
      <name>Standard CTI Enabled</name>
      </userGroup>
      </associatedGroups>

      May look like this when represented as keyword arguments with a Python dictionary as the value:
      associatedGroups={‘userGroup’ : [{‘name’ : ‘Standard CCM End User’}, {‘name’ : ‘Standard CCM End User’}]}

      suds-jurko supports the logging module, so you can also turn this on and view all of your requests, and there the mistakes might appear obvious.

    • If you’re not getting errors, but you don’t see any actual changes, you’ve probably screwed up your syntax. It can be a little trial and error (in fact, it was for me writing this post too, especially with the user groups).
  • These scripts won’t work as written if your CUCM has SAML SSO activated. I used a dCloud instance and deactivated SSO. For SSO you’ll need to modify the boilerplate code to be the following. This ensures the authentication header is included in each request suds sends. (https://stackoverflow.com/questions/11742494/python-soap-client-wsdl-call-with-suds-gives-transport-error-401-unauthorized-f)
from suds.client import Client
from suds.xsd.doctor import Import
from suds.xsd.doctor import ImportDoctor
from suds.plugin import MessagePlugin
import base64

wsdl = 'file:///C:/Development/axlsqltoolkit/schema/current/AXLAPI.wsdl'
location = 'https://cucm1.dcloud.cisco.com:8443/axl/'
username = 'amckenzie'
password = 'dCloud12345!'

base64string = base64.encodestring('%s:%s' % (username, password)).replace('n', '')
authenticationHeader = {
    "SOAPAction" : "ActionName",
    "Authorization" : "Basic %s" % base64string
}


tns = 'http://schemas.cisco.com/ast/soap/'
imp = Import('http://schemas.xmlsoap.org/soap/encoding/',
             'http://schemas.xmlsoap.org/soap/encoding/')
imp.filter.add(tns)

client = Client(wsdl,location=location,faults=False,plugins=[ImportDoctor(imp)],
                headers=authenticationHeader)

This Post Has 4 Comments

  1. Unknown

    This comment has been removed by the author.

  2. floyder

    Hello, nice information. Thanks!

    Maybe you can help me here. I'm trying to achieve the following:

    For every phone in a list, I need to know which user is controlling that device.

    I already know how to get phone lists and user lists. I have been able also to get all the details of a particular phone and user (i haven't been able of extracting only two or three fields with getPhone or getUser).

    I guess one method is looping through the phones in the list, and looping inside through all the users. Inside this loop, we could calculate the length of the controlled devices list for the user, and finally loop again through all the controlled devices of the user. If the devicename in controlled devices matches phone devicename, that's it.

    But I found this method quite intensive, since I have previously had to get all the info for all the phones and all the users from CUCM. Additionally I have had to loop through all the users for every phone in the list. I dont know if its a more efficient way to accomplish this.

    Thanks and best regards.

  3. Paul T

    Hi Floyder,

    With this you're best off using the "executeSQLQuery" method as this is much more easily pulled direct from the database. I tend to build up a query and test it on the command line before I use it in a script.

    Check out this link:
    https://www.cisco.com/c/en/us/support/docs/unified-communications/unified-communications-manager-callmanager/117780-technote-cucm-00.html

    The first query they list there is with an application user, but if you change it to enduser you'll likely get what you want. Google for the CUCM Database dictionary to get more details about the table relationships(i.e. enduser to enduserdevicemap, etc).

Comments are closed.