Make Your Cisco IP Phone Ring Using .NET

Applies To: C#, VB.NET, Cisco Phones

I often get interrupted during the day. This is irritating but a part of office life and you get used to it. What I can’t seem to get used to, however, is hearing the same 3 hour story about my coworker’s dog’s stranger anxiety and all the mundane solutions they tried in order to fix poor Rover and even though that veterinarian is a “sweetheart” they just don’t know what they’re talking about sometimes blah blah blah – EVERY SINGLE DAY OF MY LIFE. I often find myself in conversations I neither started nor encouraged to continue that have long since passed the polite listening timeout.

Generally a good strategy is to get a friendly coworker to come and rescue you. Unfortunately, they may not always be around or may not have noticed. Another option is to fake a call. If you’ve got a Cisco IP Phone sitting on your desk and don’t mind writing a little code, you can have a handy app in just a few minutes that can send disarm the Chatinators*. Even if you are able to fully function in society without the help of fake social cues, you might find it interesting what you can do with that phone on your desk.

Cisco IP Phones can accept a wide variety of commands and it’s worth taking a look at the documentation sometime. The basic idea, however, is to send the phone an HTTP Post with some XML. In this case we are going to use the ExecuteItem command with a URI. That URI will contain a Play command. Sound confusing? It is a little, but that’s why I’m going to provide the code for you to cut and paste.

To send a command using VB.NET, you can use this helper function:

    Private Function SendCommand(Address As String, Command As String, Username As String, Password As String) As String

        Dim ResponseXML As String = String.Empty

        Dim request As HttpWebRequest = WebRequest.Create(String.Format("http://{0}/CGI/Execute", Address))
        request.Timeout = 30 * 1000
        request.Method = "POST"
        request.Accept = "*/*"
        request.ContentType = "application/x-www-form-urlencoded"
        request.Credentials = New NetworkCredential(Username, Password)
        request.PreAuthenticate = True

        Dim bytes As Byte() = Encoding.UTF8.GetBytes(String.Format("XML={0}", HttpUtility.UrlEncode(Command)))
        Using outStream As Stream = request.GetRequestStream
            outStream.Write(bytes, 0, bytes.Length)
            outStream.Close()
        End Using

        Using response As WebResponse = request.GetResponse
            Using responseStream As Stream = response.GetResponseStream
                Using reader As New StreamReader(responseStream)
                    If reader IsNot Nothing Then
                        ResponseXML = reader.ReadToEnd
                        reader.Close()
                    End If
                    responseStream.Close()
                End Using
            End Using
            response.Close()
        End Using

        Return ResponseXML
    End Function

In line 5 we setup the HttpWebRequest object to send the POST to the phone. The URL that accepts the commands is either your phone’s IP Address or DNS entry followed by “/CGI/Execute“. To find your phone’s IP Address, press the settings button on the device. There should be a Phone Information section that will have your phone’s address. You may also see an entry for Host Name. This is the name of your phone and will often be the DNS entry for it. In my case it was the fully qualified version of this host name. So SEP#####.domain.com. If you are unsure, just use the IP Address and look at the response in Fiddler or something similar.

Lines 6-11 setup all the required properties to make this POST acceptable to the phone. Depending on your network settings, you’ll need to provide a username and password. This means writing programs that cause other people’s phones to ring or display funny pictures is going to be extra hard. For our phones, our AD accounts were all that was needed to authenticate with the phones. If you were given a website to configure your phone’s address book or speed dials, it’s going to be the same login information. The PreAuthenticate setting is not required, but does reduce the number of 401 challenge responses when sending multiple commands in succession.

We write out the body of the response in lines 13-17 using UTF8 and a URL Encoded XML String that starts with XML=. Finally we close the request and capture the phone’s response as XML and return it in lines 19-32.

Okay, so now we can send a command, but what does the command look like? A basic play command looks like this:

<CiscoIPPhoneExecute><ExecuteItem Priority="2" URL="Play:Classic1.raw" /></CiscoIPPhoneExecute>

It’s pretty straightforward XML. The ExecuteItem element has 2 attributes, Priority and URL. The Priority attribute can be set from 0 to 2:

  • 0 = Execute Immediately (The command takes priority over anything else the phone might be doing)
  • 1 = Execute When Idle (The command waits until the phone isn’t busy before executing)
  • 2 = Execute If Idle (The command executes if the phone isn’t busy, otherwise it’s ignored)

For a fake ring program, priority 2 is best. That way you don’t get any extra ringing if someone actually is trying to call you.

The second attribute, URL, can take an actual URL to more commands or a simple URI depending on what your phone accepts. More information can be found in that documentation I mentioned, but for what we’re doing a simple Play followed by a colon and the name of the ringtone file takes care of things.

So now you’ve got the command and a send command function. You can write whatever fancy code you want to wrap these things up. I’ve written a little taskbar app that listens for a global key press and sends rings in a configurable loop to the phone. This allows me to secretly reach for the keyboard while the talker is distracted. Most of that’s beyond this article, but I will show you my Ring method and let you fill in the blanks:

    Private Sub Ring()
        If String.IsNullOrEmpty(My.Settings.PhoneIP) Then
            ShowSettings()
        Else
            Try
                For i As Integer = 0 To My.Settings.RingRepeat - 1
                    SendCommand(My.Settings.PhoneIP, String.Format("<CiscoIPPhoneExecute><ExecuteItem Priority=""2"" URL=""Play:{0}"" /></CiscoIPPhoneExecute>", My.Settings.RingTone), My.Settings.Username, My.Settings.Password)
                    If i < My.Settings.RingRepeat - 1 Then Threading.Thread.Sleep(3000)
                Next

            Catch wex As WebException
                MsgBox("Error when talking to the phone, please check your settings!" & vbCrLf & "(Probably your credentials)" & vbCrLf & vbCrLf & wex.ToString, MsgBoxStyle.Critical, "No Ring Ring :(")
                ShowSettings()
            Catch ex As Exception
                MsgBox("Error when talking to the phone, please check your settings!" & vbCrLf & vbCrLf & ex.ToString, MsgBoxStyle.Critical, "No Ring Ring :(")
                ShowSettings()
            End Try
        End If
    End Sub

The ShowSettings method is just a helper method that instantiates a Windows Form to allow some configuration. You can do something similar or just hardcode everything. Lines 6-9 are the important lines, everything else is just error handling with the assumption that the settings are wrong.

In a loop corresponding to the number of rings we want, I call line 7. This is just our SendCommand function from above. Then I wait 3 seconds and do it again.

That should get you started. Pretty soon you’ll be interrupting Talkaholics with ease. There are actually several really cool things you can do with your phone and the SendCommand function above should help you get going.

One last thing, I did a bunch of guess work with the names of the ringtones in my phone. These are configured by your administrator and may be totally different for you, but here are the ringtone filenames I found worked for me:

  • AreYouThere.raw
  • Analog1.raw
  • Analog2.raw
  • Bass.raw
  • Chime.raw
  • CiscoStandard.raw
  • CiscoSymphonic.raw
  • CiscoTechno.raw
  • Classic1.raw
  • Classic2.raw
  • ClockShop.raw
  • Drums1.raw
  • Drums2.raw
  • FilmScore.raw
  • HarpSynth.raw
  • Jamaica.raw
  • KotoEffect.raw
  • MusicBox.raw
  • Piano1.raw
  • Pop.raw
  • Pulse1.raw
  • Sax1.raw
  • Sax2.raw
  • Vibe.raw

I should note that for whatever reason sending Piano2.raw crashed my entire phone. Also, just for fun, you can take a screenshot of your phone by using the following address in your browser: http://YOURPHONEIP/CGI/Screenshot

*Chatinators © 2012 (and for all time), Chris Kent

Change Your Formatted XML’s Encoding

Apples To: .NET (C#, VB.NET)

In my previous post, Prettify Your XML in .NET I showed a method for taking some XML and making it pretty (indentation, new lines, etc.). Using the method also produced the XML Declaration node for us. Unfortunately, because strings are UTF-16 encoded in .NET, the XML Declaration node generated by this method is always listed as “utf-16” which may not always be what we want.

Here’s the results of the previous post’s prettified XML:

<?xml version="1.0" encoding="utf-16"?>
<TMNT>
    <Turtles>
        <Turtle Name="Leonardo" Color="Blue" Weapon="Katana" />
        <Turtle Name="Raphael" Color="Red" Weapon="Sai" />
        <Turtle Name="Michelangelo" Color="Orange" Weapon="Nunchaku" />
        <Turtle Name="Donatello" Color="Purple" Weapon="Bo" />
    </Turtles>
</TMNT>

As mentioned you can see that encoding=”utf-16″. But what it you want something else (Most likely UTF8)? Well, there are several ways you can do it with Streams, XMLWriter and XMLWriterSettings objects and other junk, but you can also use a neat little method I found on Project 20 which involves subclassing the StringWriter class. (This idea originally comes from Jon Skeet).

So, just add a new class to your project and call it StringWriterWithEncoding or something similar and override the Encoding property. Here is the entire class:

Public Class StringWriterWithEncoding
    Inherits IO.StringWriter

    Private _encoding As System.Text.Encoding

    Public Sub New(encoding As System.Text.Encoding)
        MyBase.New()
        _encoding = encoding
    End Sub

    Public Sub New(encoding As System.Text.Encoding, formatProvider As IFormatProvider)
        MyBase.New(formatProvider)
        _encoding = encoding
    End Sub

    Public Sub New(encoding As System.Text.Encoding, sb As System.Text.StringBuilder)
        MyBase.New(sb)
        _encoding = encoding
    End Sub

    Public Sub New(encoding As System.Text.Encoding, sb As System.Text.StringBuilder, formatProvider As IFormatProvider)
        MyBase.New(sb, formatProvider)
        _encoding = encoding
    End Sub

    Public Overrides ReadOnly Property Encoding As System.Text.Encoding
        Get
            Return _encoding
        End Get
    End Property

End Class

So all we’ve really done is provided constructors that allow us to specify the encoding the StringWriter object should use. Then we’ve overriden the Encoding property to always return the value specified in the constructor. The result is the StringWriter uses our encoding. So then we can take the PrettyXML code and swap the StringWriter object creation to a StringWriterWithEncoding like so:

    Private Function PrettyXML(XMLString As String) As String
        Dim sw As New StringWriterWithEncoding(System.Text.Encoding.UTF8)
        Dim xw As New XmlTextWriter(sw)
        xw.Formatting = Formatting.Indented
        xw.Indentation = 4
        Dim doc As New XmlDocument
        doc.LoadXml(XMLString)
        doc.Save(xw)
        Return sw.ToString()
    End Function

Then when we run our XML through it we get the results we wanted:

<?xml version="1.0" encoding="utf-8"?>
<TMNT>
    <Turtles>
        <Turtle Name="Leonardo" Color="Blue" Weapon="Katana" />
        <Turtle Name="Raphael" Color="Red" Weapon="Sai" />
        <Turtle Name="Michelangelo" Color="Orange" Weapon="Nunchaku" />
        <Turtle Name="Donatello" Color="Purple" Weapon="Bo" />
    </Turtles>
</TMNT>

Prettify Your XML in .NET

Applies To: .NET (C#, VB.NET)

If you do much work with XML in either VB.NET or C# you’re probably looking for a way to control it’s formatting and make it look “pretty”. This has come up a few times for me so I thought I’d share a quick method for doing this.

Most often I’m using this to format XML from Web Services (Mostly SharePoint) or to take a look at XML I’ve generated for Web Services to see what’s wrong. But for this example, I’ve got a couple of helper functions that generate some XML using objects from the System.Xml namespace. Here’s how I generate the XML used here:

    Private Function GetXML() As String
        Dim doc As New XmlDocument
        Dim rn As XmlNode = doc.CreateElement("TMNT")
        Dim sn As XmlNode = doc.CreateElement("Turtles")
        sn.AppendChild(CreateTurtleNode(doc, "Leonardo", "Blue", "Katana"))
        sn.AppendChild(CreateTurtleNode(doc, "Raphael", "Red", "Sai"))
        sn.AppendChild(CreateTurtleNode(doc, "Michelangelo", "Orange", "Nunchaku"))
        sn.AppendChild(CreateTurtleNode(doc, "Donatello", "Purple", "Bo"))
        rn.AppendChild(sn)
        doc.AppendChild(rn)
        Return doc.InnerXml
    End Function

    Private Function CreateTurtleNode(doc As XmlDocument, Name As String, Color As String, Weapon As String) As XmlNode
        Dim tn As XmlNode = doc.CreateElement("Turtle")
        Dim na As XmlAttribute = doc.CreateAttribute("Name")
        na.Value = Name
        tn.Attributes.Append(na)
        Dim ca As XmlAttribute = doc.CreateAttribute("Color")
        ca.Value = Color
        tn.Attributes.Append(ca)
        Dim wa As XmlAttribute = doc.CreateAttribute("Weapon")
        wa.Value = Weapon
        tn.Attributes.Append(wa)
        Return tn
    End Function

This is just sample code to get some unformatted XML and if you display the results of the GetXML function, here’s what you get:

<TMNT><Turtles><Turtle Name=”Leonardo” Color=”Blue” Weapon=”Katana” /><Turtle Name=”Raphael” Color=”Red” Weapon=”Sai” /><Turtle Name=”Michelangelo” Color=”Orange” Weapon=”Nunchaku” /><Turtle Name=”Donatello” Color=”Purple” Weapon=”Bo” /></Turtles></TMNT>

This isn’t terrible and if you’re just using this in your code, no worries! But if you want to display this to an end user or even yourself, proper lines and indentation can make a huge difference – especially since your XML is almost guaranteed to be more complex than my example above.

There are some crazy examples out there of reading through the string and manually inserting line returns and spaces when detecting the less than or greater than symbols. These are usually error prone and won’t take into account all the various possibilities for XML. They’re inefficient, ugly, and lame. Fortunately, there are some helpful objects in the System.IO namespace and the System.Xml namespace that make all of this very easy. Here’s the function:

    Private Function PrettyXML(XMLString As String) As String
        Dim sw As New StringWriter()
        Dim xw As New XmlTextWriter(sw)
        xw.Formatting = Formatting.Indented
        xw.Indentation = 4
        Dim doc As New XmlDocument
        doc.LoadXml(XMLString)
        doc.Save(xw)
        Return sw.ToString()
    End Function

I imagine this could be improved (feel free to share in the comments), but it definitely does the job. The key elements are the XmlTextWriter’s properties Formatting and Indentation. There are several other properties and methods you can use to customize even further, but the above produces a fairly nice result:

<?xml version="1.0" encoding="utf-16"?>
<TMNT>
    <Turtles>
        <Turtle Name="Leonardo" Color="Blue" Weapon="Katana" />
        <Turtle Name="Raphael" Color="Red" Weapon="Sai" />
        <Turtle Name="Michelangelo" Color="Orange" Weapon="Nunchaku" />
        <Turtle Name="Donatello" Color="Purple" Weapon="Bo" />
    </Turtles>
</TMNT>

Now all the lines and indentation are there as expected! We even get the nice XML Declaration free of charge.