Recently I have needed to get the IP Address of the IP Phones from a Cisco Call Manager version 6.x
On previous versions of the platform (version 4) you should simply call a page DeviceListX.asp (under windows) to return an XML document similar to the following:
<?xml version="1.0" encoding="iso-8859-1"?>
<DeviceList>
<Device t="115" n="SEP0018BA14AF7E" d="4330 - Sunnyvale Netlab" c="LONG-INTERNATIONAL-CSS" p="LONG-DP" i="" s="" />
<Device t="115" n="SEP001E4AA8CC00" d="4305 - Scott Sharp" c="LONG-INTERNATIONAL-CSS" p="LONG-DP" i="10.1.206.160" s="1" />
<Device t="35" n="SEP000E38502B1D" d="Auto 105139" c="SYSTEM-TAPS-CSS" p="SNVL-TAPS-DP" i="" s="" />
<Device t="36" n="SEP000BFDB81799" d="Auto 105300" c="SYSTEM-TAPS-CSS" p="SNVL-TAPS-DP" i="" s="" />
</DeviceList>
Well I had a lot of problems trying to simply update my code to use the new method which was to use a technology called AXL. AVVID XML Layer (AXL) is a Cisco application programming interface (API) and web service designed to give applications access to Cisco CallManager configuration and provisioning services
For more details on AXL see http://developer.cisco.com/web/axl/home and http://developer.cisco.com/web/axl/docs
Now from what I have gathered to return the IP Address of the phones you need to do two things:
- Get a list of the Devices (phones)
- Query the RipPort service to get the real time information. the IP Address is stored in this "in memory" database
Below I will attempt to document the process I took to get this to work for me on version 6.x
To start off with
The basic idea is this:
- Post a request to the Call Manager on port 8443.
- The request must be made using HTTPS
- The URI is /realtimeservice/services/{servicename}
So in my case the url was:
https://10.3.4.22:8443/realtimeservice/services/RisPort
Note: The URL is case sensitive since it is running on a Linux OS
You then need to add an HTTP Header called "SOAPAction" and provide it with the name of a SOAP action
SOAPAction : http://schemas.cisco.com/ast/soap/action/#RisPort#SelectCmDevice
Note: At this time I do not know much about this SOAPAction
You then need to craft a SOAP request and POST that to the server.
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:ExecuteCCMSQLStatement soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://schemas.cisco.com/ast/soap/">
<ExecuteSQLInputData xsi:type="xsd:string">select name,description from Device where tkclass = 1 order by name;</ExecuteSQLInputData>
<GetColumns soapenc:arrayType="ns1:ColumnType[2]" xsi:type="soapenc:Array" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<item xsi:type="ns1:ColumnType">
<Name xsi:type="xsd:string">name</Name>
</item>
<item xsi:type="ns1:ColumnType">
<Name xsi:type="xsd:string">description</Name>
</item>
</GetColumns>
</ns1:ExecuteCCMSQLStatement>
</soapenv:Body>
</soapenv:Envelope>
As you can see from the above XML I am actually issuing a SQL Query in the SOAP request.
select name,description from Device where tkclass = 1 order by name
There is a table called "Device" which holds all of the devices of the call manager.
I do not know what tkclass is for at this time.
Basically posting that request will return the name, description of the devices in your call manager in a format similar to the following:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:ExecuteCCMSQLStatementResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://schemas.cisco.com/ast/soap/">
<ExcuteSQLOutputData soapenc:arrayType="ns1:ColumnValueType[186]" xsi:type="soapenc:Array" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<item xsi:type="ns1:ColumnValueType">
<Name xsi:type="ns1:ColumnNType">name</Name>
<Value xsi:type="ns1:ColumnVType">ATA002155021DA5</Value>
</item>
<item xsi:type="ns1:ColumnValueType">
<Name xsi:type="ns1:ColumnNType">description</Name>
<Value xsi:type="ns1:ColumnVType">4131 - XYZ Fax Machine</Value>
</item>
<item xsi:type="ns1:ColumnValueType">
<Name xsi:type="ns1:ColumnNType">name</Name>
<Value xsi:type="ns1:ColumnVType">SEP000BFDCDBA07</Value>
</item>
<item xsi:type="ns1:ColumnValueType">
<Name xsi:type="ns1:ColumnNType">description</Name>
<Value xsi:type="ns1:ColumnVType">3931 - Chris Crowe</Value>
</item>
</ExcuteSQLOutputData>
</ns1:ExecuteCCMSQLStatementResponse>
</soapenv:Body>
</soapenv:Envelope>
I then parsed this XML and returned a simple DataTable with the details
DataTable dt = new DataTable();
dt.Columns.Add("name", typeof(string));
dt.Columns.Add("description", typeof(string));
XmlDocument doc = new XmlDocument();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
nsmgr.AddNamespace("ns1", "http://schemas.cisco.com/ast/soap/");
doc.LoadXml(XML);
XmlNodeList items = doc.SelectNodes("/soapenv:Envelope/soapenv:Body/ns1:ExecuteCCMSQLStatementResponse/ExcuteSQLOutputData/item/Value", nsmgr);
for (int Index = 0; Index < items.Count; Index += 2)
{
XmlNode Name = items[Index];
XmlNode Description = items[Index+1];
DataRow dr = dt.NewRow();
dr.ItemArray = new object[] { Name.InnerText, Description.InnerText };
dt.Rows.Add(dr);
}
return dt;
So at this point we have a DataTable with one row for each device but no IP Address
So we now build another AXL request similar to the following:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:SelectCmDevice soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://schemas.cisco.com/ast/soap/">
<StateInfo xsi:type="xsd:string"/>
<CmSelectionCriteria xsi:type="ns1:CmSelectionCriteria">
<MaxReturnedDevices xsi:type="xsd:unsignedInt">200</MaxReturnedDevices>
<Class xsi:type="xsd:string">Phone</Class>
<Model xsi:type="xsd:unsignedInt">255</Model>
<Status xsi:type="xsd:string">Registered</Status>
<NodeName xsi:type="xsd:string"/>
<SelectBy xsi:type="xsd:string">Name</SelectBy>
<SelectItems soapenc:arrayType="ns1:SelectItem[3]" xsi:type="soapenc:Array" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<item xsi:type="ns1:SelectItem">
<Item xsi:type="xsd:string">ATA002155021DA5</Item>
</item>
<item xsi:type="ns1:SelectItem">
<Item xsi:type="xsd:string">SEP000BFDCDBA07</Item>
</item>
<item xsi:type="ns1:SelectItem">
<Item xsi:type="xsd:string">SEP000C8548D65D</Item>
</item>
</SelectItems>
</CmSelectionCriteria>
</ns1:SelectCmDevice>
</soapenv:Body>
</soapenv:Envelope>
Some points about the above request:
- I believe that there is a limit of 200 devices being requested, but I have not been able to test it yet as our current system has less than 50 devices on it.
- The SelectItems tag contains soapenc:arrayType="ns1:SelectItem[3]" the [3] means that there is going to be 3 SelectItem nodes specified later. If you request 50 then this needs to be set to soapenc:arrayType="ns1:SelectItem[50]"
- Each device you want information on requires one of the following specifying the device name
<item xsi:type="ns1:SelectItem"> <Item xsi:type="xsd:string">{DeviceName}</Item> </item>
SOURCE CODE
Below is the full source to a small library I created to help me get the Phone details and IP Addreses in a format that is the same as the old DeviceListX.asp script.
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Data;
using System.Xml;
using System.Reflection;
namespace AXLLibrary
{
public enum AXLServiceType
{
RisPort = 1
}
public class AXLClass
{
public string Username { get; set; }
public string Password { get; set; }
public string ServerNameOrIPAddress { get; set; }
public AXLClass(string Username, string Password, string ServerNameOrIPAddress)
{
this.Username = Username;
this.Password = Password;
this.ServerNameOrIPAddress = ServerNameOrIPAddress;
}
// Because of a broken cert we will get an error without ignoring the errors which we do below.
private bool ServerCertificateValidationCallbackMethod(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
//// Always accept
//if (sslPolicyErrors != SslPolicyErrors.None)
//{
// Message("<div><font color='red'>SSL Certificate Issues</div><ul>");
// Message("<LI>Policy Error -> <b>" + sslPolicyErrors.ToString() + "</b></LI>");
// Message("<LI>Issuer -> <b>" + certificate.Issuer + "</b></LI>");
// Message("<LI>Expires -> <b>" + certificate.GetExpirationDateString() + "</b></LI>");
// Message("<LI>Subject -> <b>" + certificate.Subject + "</b></LI>");
//}
return true;
}
public string ExecuteSOAPRequest(string SOAPAction, string SoapRequest, AXLServiceType ServiceType)
{
Validate();
try
{
ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallback(ServerCertificateValidationCallbackMethod);
ServicePointManager.Expect100Continue = true;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(GetServiceURI(ServiceType));
req.KeepAlive = false;
req.Credentials = GetCredentials();
req.Headers.Add("SOAPAction", "http://schemas.cisco.com/ast/soap/action/" + SOAPAction);
req.ContentType = "text/xml";
req.Accept = "text/xml";
req.Method = "POST";
Stream s = req.GetRequestStream();
s.Write(System.Text.Encoding.ASCII.GetBytes(SoapRequest), 0, SoapRequest.Length);
s.Close();
WebResponse resp = req.GetResponse();
StreamReader sr = new StreamReader(resp.GetResponseStream());
string XMLResult = sr.ReadToEnd();
return XMLResult;
}
catch (WebException ex)
{
throw ex;
}
}
private ICredentials GetCredentials()
{
return new NetworkCredential(Username, Password);
}
private Uri GetServiceURI(AXLServiceType Service)
{
string URI = string.Format("https://{0}:8443/realtimeservice/services/{1}", ServerNameOrIPAddress, Service.ToString());
return new Uri(URI);
}
private void Validate()
{
if (Username.Length == 0)
throw new Exception("Username must have a value!");
if (Password.Length == 0)
throw new Exception("Password must have a value!");
if (ServerNameOrIPAddress.Length == 0)
throw new Exception("ServerNameOrIPAddress must have a value!");
}
public DataTable GetDevices()
{
DataTable dt = new DataTable();
dt.Columns.Add("name", typeof(string));
dt.Columns.Add("description", typeof(string));
Assembly assembly = Assembly.GetExecutingAssembly();
Stream stream = assembly.GetManifestResourceStream("AXLLibrary.GetDevices.xml");
StreamReader streamReader = new StreamReader(stream);
string soapRequest = streamReader.ReadToEnd();
string XML = ExecuteSOAPRequest("#RisPort#SelectCmDevice", soapRequest, AXLServiceType.RisPort);
XmlDocument doc = new XmlDocument();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
nsmgr.AddNamespace("ns1", "http://schemas.cisco.com/ast/soap/");
doc.LoadXml(XML);
XmlNodeList items = doc.SelectNodes("/soapenv:Envelope/soapenv:Body/ns1:ExecuteCCMSQLStatementResponse/ExcuteSQLOutputData/item/Value", nsmgr);
for (int Index = 0; Index < items.Count; Index += 2)
{
XmlNode Name = items[Index];
XmlNode Description = items[Index+1];
DataRow dr = dt.NewRow();
dr.ItemArray = new object[] { Name.InnerText, Description.InnerText };
dt.Rows.Add(dr);
}
return dt;
}
public DataTable GetDeviceIPAddresses()
{
DataTable dt2 = new DataTable();
dt2.Columns.Add("name", typeof(string));
dt2.Columns.Add("Description", typeof(string));
dt2.Columns.Add("IpAddress", typeof(string));
dt2.Columns.Add("DirNumber", typeof(string));
dt2.Columns.Add("Class", typeof(string));
dt2.Columns.Add("Model", typeof(string));
dt2.Columns.Add("Product", typeof(string));
DataTable dt3 = new DataTable();
dt3.Columns.Add("name", typeof(string));
dt3.Columns.Add("Description", typeof(string));
dt3.Columns.Add("IpAddress", typeof(string));
dt3.Columns.Add("DirNumber", typeof(string));
dt3.Columns.Add("Class", typeof(string));
dt3.Columns.Add("Model", typeof(string));
dt3.Columns.Add("Product", typeof(string));
DataTable Devices = GetDevices();
Assembly assembly = Assembly.GetExecutingAssembly();
Stream stream = assembly.GetManifestResourceStream("AXLLibrary.getDeviceIPAddress.xml");
StreamReader streamReader = new StreamReader(stream);
string soapRequest = streamReader.ReadToEnd();
// Set the # of devices items we are going to be searching for
soapRequest = soapRequest.Replace("{0}", Devices.Rows.Count.ToString());
StringBuilder TemplateData = new StringBuilder();
string Template = "<item xsi:type=\"ns1:SelectItem\"><Item xsi:type=\"xsd:string\">{0}</Item></item>";
foreach (DataRow dr in Devices.Rows)
TemplateData.AppendFormat(Template, dr[0].ToString());
// Set the templates
soapRequest = soapRequest.Replace("{1}", TemplateData.ToString());
string XML = ExecuteSOAPRequest("#RisPort#SelectCmDevice", soapRequest, AXLServiceType.RisPort);
XmlDocument doc = new XmlDocument();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
nsmgr.AddNamespace("ns1", "http://schemas.cisco.com/ast/soap/");
doc.LoadXml(XML);
XmlNodeList items = doc.SelectNodes("/soapenv:Envelope/soapenv:Body/ns1:SelectCmDeviceResponse/SelectCmDeviceResult/CmNodes/item/CmDevices/item", nsmgr);
foreach (XmlNode item in items)
{
string Name = GetNodeValue(item, "Name");
string Description = GetNodeValue(item, "Description");
string IpAddress = GetNodeValue(item,"IpAddress");
string DirNumber = GetNodeValue(item, "DirNumber");
string Class = GetNodeValue(item, "Class");
string Model = GetNodeValue(item, "Model");
string Product = GetNodeValue(item, "Product");
DataRow dr = dt2.NewRow();
dr.ItemArray = new object[] { Name, Description, IpAddress, DirNumber, Class, Model, Product };
dt2.Rows.Add(dr);
}
DataSet ds = new DataSet();
DataSetHelper dsHelper = new DataSetHelper(ref ds);
ds.Tables.Add(Devices);
ds.Tables.Add(dt2);
ds.Tables.Add(dt3);
ds.Relations.Add("NameName", ds.Tables[0].Columns[0], ds.Tables[1].Columns[0]);
foreach(DataRow dr in ds.Tables[0].Rows)
{
DataRow newDataRow = dt3.NewRow();
DataRow[] Children = dr.GetChildRows(ds.Relations[0]);
if (Children.Length == 0)
newDataRow.ItemArray = dr.ItemArray;
else
newDataRow.ItemArray = Children[0].ItemArray;
dt3.Rows.Add(newDataRow);
}
return dt3;
}
private string GetNodeValue(XmlNode item, string Key)
{
XmlNode node = item.SelectSingleNode(Key);
return node.InnerText;
}
public string GetDeviceListX()
{
DataTable dt = GetDeviceIPAddresses();
StringWriter sw = new StringWriter();
XmlTextWriter writer = new XmlTextWriter(sw);
writer.Formatting = Formatting.Indented;
//Write the root element
writer.WriteRaw("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>");
//Start an element
writer.WriteStartElement("DeviceList");
foreach (DataRow dr in dt.Rows)
{
writer.WriteStartElement("Device");
writer.WriteAttributeString("t", dr["Model"].ToString());
writer.WriteAttributeString("n", dr["name"].ToString());
writer.WriteAttributeString("d", dr["description"].ToString());
writer.WriteAttributeString("c", "");
writer.WriteAttributeString("p", "");
writer.WriteAttributeString("i", dr["IPAddress"].ToString());
writer.WriteAttributeString("s", (dr["IPAddress"].ToString().Length == 0) ? "" : "1");
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.Flush();
writer.Close();
return sw.ToString();
}
}
}
The source above loads XML fragments from to embedded resources:
GetDevices.xml
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:ExecuteCCMSQLStatement soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://schemas.cisco.com/ast/soap/">
<ExecuteSQLInputData xsi:type="xsd:string">select name,description from Device where tkclass = 1 order by name;</ExecuteSQLInputData>
<GetColumns soapenc:arrayType="ns1:ColumnType[2]" xsi:type="soapenc:Array" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<item xsi:type="ns1:ColumnType">
<Name xsi:type="xsd:string">name</Name>
</item>
<item xsi:type="ns1:ColumnType">
<Name xsi:type="xsd:string">description</Name>
</item>
</GetColumns>
</ns1:ExecuteCCMSQLStatement>
</soapenv:Body>
</soapenv:Envelope>
getDeviceIPAddress.xml
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns1:SelectCmDevice soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://schemas.cisco.com/ast/soap/">
<StateInfo xsi:type="xsd:string"/>
<CmSelectionCriteria xsi:type="ns1:CmSelectionCriteria">
<MaxReturnedDevices xsi:type="xsd:unsignedInt">200</MaxReturnedDevices>
<Class xsi:type="xsd:string">Phone</Class>
<Model xsi:type="xsd:unsignedInt">255</Model>
<Status xsi:type="xsd:string">Registered</Status>
<NodeName xsi:type="xsd:string"/>
<SelectBy xsi:type="xsd:string">Name</SelectBy>
<SelectItems soapenc:arrayType="ns1:SelectItem[{0}]" xsi:type="soapenc:Array" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
{1}
</SelectItems>
</CmSelectionCriteria>
</ns1:SelectCmDevice>
</soapenv:Body>
</soapenv:Envelope>
Sample Usage
AXLLibrary.AXLClass lib = new AXLLibrary.AXLClass("Username", "password", "10.3.24.22");
string XML = lib.GetDeviceListX();
MessageBox.Show(XML);
The output from the sample would look like this which is in the same XML format as the old devicelistx.asp script returned.
<?xml version="1.0" encoding="iso-8859-1"?>
<DeviceList>
<Device t="115" n="SEP0018BA14AF7E" d="4330 - Sunnyvale Netlab" c="LONG-INTERNATIONAL-CSS" p="LONG-DP" i="" s="" />
<Device t="115" n="SEP001E4AA8CC00" d="4305 - Scott Sharp" c="LONG-INTERNATIONAL-CSS" p="LONG-DP" i="10.1.206.160" s="1" />
<Device t="35" n="SEP000E38502B1D" d="Auto 105139" c="SYSTEM-TAPS-CSS" p="SNVL-TAPS-DP" i="" s="" />
<Device t="36" n="SEP000BFDB81799" d="Auto 105300" c="SYSTEM-TAPS-CSS" p="SNVL-TAPS-DP" i="" s="" />
</DeviceList>
One tip
Connect to the SOAP Monitor service on the call manager which exists at a URL like this and if you leave it overnight running you will see a number of requests which the system generates or if you create your own requests and you get back HTTP 500 errors this monitor may help as well.
https://servername:8443/realtimeservice/SOAPMonitor
I hope this source helps someone out there....