Welcome to MSDN Blogs Sign in | Join | Help

AutoAttendant: What do the various Greetings mean

This post is going to give an explanation of what the various greetings mean, for the autoattendant.

The posting will be divided into two sections. First, I will describe the common structure. Next, I will describe how this manifests itself at runtime for the DTMF AutoAttendant, and the ASR AutoAttendant respectively.

 

Greeting Configuration for the AutoAttendant

The greetings are configured for the AutoAttendant using the set-umautoattendant command in Powershell, or by using the Exchange Management Console MMC snapin that ships with Exchange Server 2007. When using set-umautoattendant, the following parameters are available to customize the greetings

 

Prompt Description
InfoAnnouncementFilename Informational Announcement
InfoAnnouncementEnabled Informational Announcement Enabled Flag
BusinessHoursWelcomeGreetingFilename
AfterHoursWelcomeGreetingFilename
Welcome Greeting Announcement
BusinessHoursWelcomeGreetingEnabled
AfterHoursWelcomeGreetingEnabled
Welcome Greeting Announcement Enabled
BusinessHoursMainMenuCustomPromptFilename
AfterHoursMainMenuCustomPromptFilename
Custom Menu Announcement
BusinessHoursMainMenuCustomEnabled
AfterHoursMainMenuCustomEnabled
Custom Menu Enabled

 

If you want to set the Autoattendant greetings using the Exchange Management Console MMC snapin, then you should navigate to the Greetings tab of the autoattendant properties page.

 

 

DTMF AutoAttendant Greeting ConfigurationMain Menu Greetings.

The Greetings for the Main Menu of the autoattendant are played in the following sequence.

 

First, the autoattendant plays the WelcomeGreeting defined on the AutoAttendant. If no greeting is defined, then the default greeting is played. The default welcome greeting is "Welcome to Microsoft Exchange Automated Attendant".

If the call was received during Holiday Hours (as defined in the holiday schedule), then the greeting associated with that holiday is played. After this, the Informational Announcement is played (if defined).

If a custom menu is defined, then the prompt  defined in the AutoAttendant's BusinessHoursMainMenuCustomPromptFilename is played. The custom menu can be defined by setting the BusinessHoursCustomMenuKeyMapping. To enable the custom menu, set the BusinessHoursCustomMenuKeyMappingEnabled property to true.

If a custom menu prompt filename is not defined, then the server will do a best effort to render a TTS (Text to Speech) greeting using the CustomMenu options that were defined.

Custom Menu TTS

When a CustomMenu prompt filename is not defined, then the system will TTS the custom menu prompt. In order to TTS the prompt, it will sort the Custom menu in increasing order by the Key that has been defined. Then it will use greeting templates defined for the language of the autoattendant to generate Greeting segments, one for each menu item. The greeting segments are then concatenated to give the final greeting.

For a normal (non-timeout) menu option, the following greeting template is used to generate the greeting segment.

For <description> Press <key>

And if you have a timeout option defined in the custom menu, then the following template is used.

Or, stay on the line for <description>

The following table shows the greeting segment that are generated from the custom menu definition. 

Key Description Greeting Segment
1 Sales For Sales Press One
2 Support For SupportPress Two
Timeout Directions Or, stay on the line for Directions

 

Once we have the greeting segment, the final greeting is generated by concatenating each individual greeting segment:

For Sales Press One. For Support, Press Two. Or, stay on the line for directions.

 

 Autoattendant Greeting Scenarios

The following section describes how the autoattendant sounds when different combinaions of greetings are configured, along with some of the other options that effect greeting playback.

 

Default Configuration

This is the case where an autoattendant was created, and no settings were modified on the autoattendant. The autoattendant will sound as follows:

Welcome to Microsoft Exchange Automated Attendant.

Custom Welcome Greeting configured

You can change the welcomg greeting of the autoattendant by specifying a Business/AfterHours WelcomeGreetingFilename, and setting the corresponding Business/AfterHours WelcomeGreetingEnabled flag to TRUE. Once you do this, the system will sound as follows:

Welcome to Contoso Corporation.

Custom Informational Greeting configured

You can configure an informational greeting to let the callers know about extraordinary situations like unforeseen closures etc.. This greeting can be configured by setting the InfoAnnouncementFilename & InfoAnnouncementEnabled properties of the autoattendant. If you set this greeting, then the system will sound like this:

Welcome to contoso corporation. We are closed due to year end Inventory processing.

 If you set the InfoAnnouncementEnabled property value to Uninteruptible, then this greeting cannot be interrupted by the caller. This means that the entire main menu greetings will become uninterruptible. This includes the welcome greeting, informational greeting, and the holiday greeting as well.

 

 Holiday Greetings

 You can also configure a holidy schedule for the autoattendant. When a call comes in during a holiday, the autoattendant will play a greeting specified for that holiday. Normally, it doesnt make sense to have both an Informational greeting and holiday greeting active at the same time, so we will assume for this scenario, that the informational greeting is not configured.

You can specify a holday schedule and greeting for the autoattendant by setting the autoattendant's HolidaySchedule property using Powershell. Or, if you are using the MMC, you can navigate to the Times tab, and set the Holiday Schedule there.

 If you specify a holiday schedule for Christmas, for example, and also specify a greeting for that holiday, then this is how the main menu will sound when a user calls in.

Welcome to Contoso Corporation. The office will be closed on the 24th and 25th of December for Christmas. Normal hours will resume from the 26th of december.

Custom Menu Greeting

 If a custom menu is defined, then the next greeting that will be played is the custom menu greeting. As explained earlier, the autoattendant will use the greeting defined in the Business/AfterHoursMainMenuCustomPromptFilename. If no greeting is defined, then a TTS greeting will be played to the caller.

If you assume that the custom menu has three options: Sales, Support, Directions as described in the section above, then this is what the caller will hear when no custom menu greeting is defined:

Welcome to Contoso Corporation. For Sales Press One. For Support, Press Two. Or, stay on the line for directions.

Additionally, if Transfer to operator and directory search are enabled for the autoattendant, the autoattendant will give the caller these options as well:

Welcome to Contoso Corporation. For Sales Press One. For Support, Press Two. Or, stay on the line for directions. To contact someone, press the Pound key. To speak to an operator, press Zero.

 If the call came in during a holiday, then the holiday greeting will be played after the welcome greeting. In that case, this is what the callers will hear:

Welcome to Contoso Corporation. The office will be closed on the 24th and 25th of December for christmas. Normal hours will resume from the 26th. of December.

For Sales Press One. For Support, Press Two. Or, stay on the line for directions. To contact someone, press the Pound key. To speak to an operator, press Zero.

However, if you define a greeting for the custom menu, then this is what the caller will hear:

Welcome to Contoso Corporation. The office will be closed on the 24th and 25th of December for christmas. Normal hours will resume from the 26th. of December.

To talk to our sales department, press One. For support issues, press Two. Or, you can stay on the line for directions.

If you compare this greeting flow with the flowchard given in the begining, you will noticed that the custom menu greeting did not give the caller an option for transferring to the operator, or doing directory search by pressing the Pound key. If you want to give callers this option, you must record these into your custom menu greeting.

 If you dont want to give callers this option, you can disable them on the autoattendant.

PowerShell script to get the reason why a Unified Messaging worker process recycled

If you are the administrator of an Exchange Unified Messaging server, you might want to know why the UM worker process is recycling and how often. The following one line PowerShell script will print out all the reasons why the process recycled:

 

 get-eventlog application | where { ($_.EventId -ge 1049 -and $_.EventId -le 1055) -or $_.EventId -eq 1092 } | ft -wrap

If you run this command, you will get an output like so:

 

 

Index Time          Type Source                EventID Message                                                             
----- ----          ---- ------                ------- -------                                                             
56821 Mar 12 20:35  Info MSExchange Unified M     1055 The Unified Messaging Worker Process was terminated because the confi
    9                    essaging                      gured maximum lifetime was exceeded.                                
56773 Mar 12 20:05  Info MSExchange Unified M     1000 The Unified Messaging Worker Process was started successfully on port
    4                    essaging                       "5065".                                                            
56772 Mar 12 20:04  Info MSExchange Unified M     1051 The Microsoft Exchange Unified Messaging service has created a new UM
    5                    essaging                       WorkerProcess because the working set (589 MB) has exceeded the conf
                                                       igured maximum: 500 MB"                                             
52146 Mar 11 03:55  Info MSExchange Unified M     1055 The Unified Messaging Worker Process was terminated because the confi
    1                    essaging                      gured maximum lifetime was exceeded.                                
52099 Mar 11 03:25  Info MSExchange Unified M     1000 The Unified Messaging Worker Process was started successfully on port
    2                    essaging                       "5067".                                                            
52098 Mar 11 03:24  Info MSExchange Unified M     1051 The Microsoft Exchange Unified Messaging service has created a new UM
    1                    essaging                       WorkerProcess because the working set (777 MB) has exceeded the conf
                                                       igured maximum: 500 MB"                                             
49992 Mar 10 02:55  Info MSExchange Unified M     1055 The Unified Messaging Worker Process was terminated because the confi
    1                    essaging                      gured maximum lifetime was exceeded.                                
49948 Mar 10 02:25  Info MSExchange Unified M     1000 The Unified Messaging Worker Process was started successfully on port
    6                    essaging                       "5065".                                                            
49947 Mar 10 02:24  Info MSExchange Unified M     1051 The Microsoft Exchange Unified Messaging service has created a new UM
    7                    essaging                       WorkerProcess because the working set (644 MB) has exceeded the conf
                                                       igured maximum: 500 MB"                                             

Viewing eventlog files in the absence of resource strings.

Many a time, I had to look at eventlog files sent by a customer, or those from a test run that happened some time back. These files (.evt) are offline copies of eventlogs. To view them, you open them up with eventviewer.

However, you might sometimes notice that the display of these logs does not give the descriptive information that was logged. It will only show the parameters used when logging the message. For eg, if you create an eventlog entry with the followin format string:

"This operation failed with error %s"

then, the display will only show the parameter that was passed to the format string.

To work around this, you can use the /AUXSOURCE= flag to point eventviewer at another machine that might have the required resources to load the resource strings. This is done by invoking the eventviewer in the following manner:

mmc.exe /a eventvwr.msc /AUXSOURCE=<machine name or ip>

Posted by Feroze Daud | 0 Comments

Attaching VS to a process on startup.

I have been stumped from time to time on how to attach VS debuggers to a process on process startup. I knew how to do it with Windbg, by setting ImageFileExecutionOptions for the target process. However I did not know how to do it for VS.

Well, I need to fret no more. A colleague forwarded me th is blog entry by Greg, in which he has some good info on how to use ImageFileExecutionOptions to attach to a process on startup.

http://blogs.msdn.com/greggm/archive/2005/02/21/377663.aspx

Posted by Feroze Daud | 0 Comments
Filed under:

Ping Part IV: Adventures in Socket programming using System.Net

In this part, we will add some networking code to the code we have thus far. When we get done, we should have a working Ping utility.

 

Take the program that we wrote in the Ping: Part III and add the following code.

 

using System;

using System.Text;

using System.Globalization;

using System.Net;

using System.Net.Sockets;

using System.Net.NetworkInformation;

 

namespace ping

{

      public class ICMP_PACKET

      {

 

            public ICMP_PACKET(Byte kind, Byte code, UInt16 id, UInt16 seq, byte[] data)

            {

                  ...

            }

 

 

 

            public ICMP_PACKET(byte[] data, int offset, int count)

            {

                  this.i_type = data[offset++];

                  this.i_code = data[offset++];

                  this.i_cksum = ByteToUint(data, offset);

                  offset += 2;

                  this.i_id = ByteToUint(data, offset);

                  offset += 2;

                  this.i_seq = ByteToUint(data, offset);

                  offset += 2;

                  this.data = new byte[count - headerLength];

                  Array.Copy(data, offset, this.data, 0, this.data.Length);

            }

 

            public static UInt16 ByteToUint(byte [] networkBytes, int offset)

            {

                  return BitConverter.ToUInt16(new byte[] { networkBytes[offset+1], networkBytes[offset] }, 0);

            }

      }

 

 

class Program

{

            static void Main(string[] args)

            {

                  if (args.Length != 1)

                  {

                        Usage();

                  }

 

                  if (0 == String.Compare(args[0], "/?", true, CultureInfo.InvariantCulture)

                  || 0 == String.Compare(args[0], "-h", true, CultureInfo.InvariantCulture)

                  || 0 == String.Compare(args[0], "-?", true, CultureInfo.InvariantCulture))

                  {

                        Usage();

                  }

 

                  string target = args[0];

 

                  IPAddress [] heTarget = Dns.GetHostAddresses(target);

                  IPAddress ipTarget = null;

                  foreach (IPAddress ip in heTarget)

                  {

                        if (ip.AddressFamily == AddressFamily.InterNetwork)

                        {

                              ipTarget = ip;

                              break;

                        }

                  }

 

                  IPAddress[] heSource = Dns.GetHostAddresses(Dns.GetHostName());

                  IPAddress ipSource = null;

                  foreach (IPAddress ip in heSource)

                  {

                        if (ip.AddressFamily == AddressFamily.InterNetwork)

                        {

                              ipSource = ip;

                              break;

                        }

                  }

 

                  IPEndPoint epLocal = new IPEndPoint(ipSource, 0);

 

                  Socket pingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);

                  pingSocket.Bind(epLocal);

 

                  byte [] data = Encoding.ASCII.GetBytes("1234567890abcdef");

 

                  Console.WriteLine("Ping {0}({1}) with {2} bytes of data...",target, ipTarget.ToString(), data.Length);

 

                  Console.WriteLine();

 

                  for (int i = 0; i < 3; i++)

                  {

 

                        ICMP_PACKET packet = ICMP_PACKET.CreateRequestPacket(111, 222, data);

 

                        IPEndPoint epRemote = new IPEndPoint(ipTarget, 0);

 

                        pingSocket.SendTo(packet.Serialize(), epRemote);

 

                        byte[] receiveData = new byte[1024];

                        EndPoint epResponse = (EndPoint)new IPEndPoint(0, 0);

                        int read = pingSocket.ReceiveFrom(receiveData, ref epResponse);

 

                        ICMP_PACKET recvPacket = new ICMP_PACKET(receiveData, 20, read);

 

                        Console.WriteLine("Reply from:{0} bytes = {1}", ((IPEndPoint)epResponse).Address.ToString(), recvPacket.PayloadLength);

 

                  }

 

 

            }

 

...

 

 

      }

 

 

A couple of important points that I wanted to bring to your attention:

 

1)      I have uncommented the code which checks the validity of command-line parameters in Program::Main.

2)      We have added a new constructor ICMP_PACKET(byte [], int, int). This is to parse the response returned by the server.

3)      The ByteToUInt(byte [], int) method is used to convert from Network Order to Host Order. This is needed because the bytes on the wire are in Network Order, which is BigEndian

4)      In Main(), when we resolve the IPAddress of the source and destination, we need to make sure that we work with the correct IP address. Since this program is limited to IPv4, we need to make sure that we do not accidently work with IPv6 addresses. The IPv6 address may be returned when the host that you are trying to resolve is IPv6 enabled.

5)      When we read the response from the socket, we get back more bytes that the size of the ICMP packet sent by the server. This is because we opened the Socket with SocketType.Raw. In this mode, we will also get back the IPv4 header that is encapsulating the ICMP packet. So, when we process the response packet, we need to make sure that we skip past the IP header. That is why we skip the first 20 bytes of the packet when we try to create an ICMP_PACKET with the returned data.

 

 

If you compile and run this program, you will be able to ping a host without any problems.

 

As utilities go, this is a very barebones utility. It does not handle errors from the network very well. For the interested, here are some things that you can do to this to make it more robust:

 

  1. Be ready to handle exceptions from Socket.SendTo() and Socket.ReceiveFrom().
  2. The utility does not handle timeouts. It would be a simple matter to add code and set a timeout on the Receive() call. That way, if the response doesnt come back within a certain time interval, you can print an error ( or an asterisk like the Ping utility that comes with the OS).
  3. Give a command line option to change the #bytes sent in the payload.

 

Well, that is it then. Now we have a working Ping utility. Hopefully you have learned how to convert a protocol specification into a working program.

 

Posted by Feroze Daud | 0 Comments

Ping Part III: Adventures in Socket programming using System.Net

 

In Part II of this article, we saw how we are going to use the ICMP protocol to implement a simple Ping client. We also saw a skeleton of this program which showed how to translate the ICMP packet specification into a C# structure.

 

In this part, we will write a routine to calculate the checksum of the packet, and a routine to serialize the packet into a byte array. Recall from PartII that the request and reply packets have a particular encoding on the wire. We will have to write a routine that will encode the packet into a byte array, so that the array can be sent on the wire.

 

Take the skeleton that we created in Part II and add the following (lines in blue):

 

using System;

using System.Text;

using System.Globalization;

 

namespace ping

{

      public class ICMP_PACKET

      {

            ...

 

            public ICMP_PACKET(Byte kind, Byte code, UInt16 id, UInt16 seq, byte[] data)

            {

                  this.i_type = kind;

                  this.i_code = code;

                  this.i_id = ToNetworkOrder(id);

                  this.i_seq = ToNetworkOrder(seq);

                  this.data = data;

                  this.i_cksum = Checksum();

            }

 

            public static ICMP_PACKET CreateRequestPacket(UInt16 id, UInt16 seq, byte[] data)

            {

                  return new ICMP_PACKET(8, 0, id, seq, data);

            }

 

            public static byte[] GetBytesInNetworkOrder(UInt16 number)

            {

                  byte[] bytes = BitConverter.GetBytes(number);

                  if (BitConverter.IsLittleEndian)

                        return new byte[] { bytes[1], bytes[0] };

                  else

                        return bytes;

            }

 

            public static UInt16 ToNetworkOrder(UInt16 number)

            {

                  byte[] networkBytes = GetBytesInNetworkOrder(number);

                  return BitConverter.ToUInt16(networkBytes, 0);

            }

 

 

            public UInt16 Checksum()

            {

                  int cksum_buffer_length = (int)(Length / 2);

 

                  byte [] packetBytes = Serialize(this);

 

                  //

                  // first, convert the serialized bytes into a UInt16 array

                  // we will use the UInt16 array to do the checksum

                  //

                  UInt16[] checksumBuffer = new UInt16[cksum_buffer_length];

 

                  int index = 0;

                  int i = 0;

                  while (index < Length)

                  {

                        checksumBuffer[i] = BitConverter.ToUInt16(packetBytes,index);

                        index += 2;

                        ++i;

                  }

 

                  int checksum = 0;

                  for (i = 0; i < checksumBuffer.Length; i++)

                  {

                        checksum += Convert.ToInt32(checksumBuffer[i]);

                  }

 

                  checksum = (checksum >> 16) + (checksum & 0xffff);

                  checksum += (checksum >> 16);

 

                  return (UInt16)(~checksum);

            }

 

            public byte[] Serialize()

            {

                  return Serialize(this);

            }

 

            public static byte[] Serialize(ICMP_PACKET packet)

            {

                  // first, find out how many bytes to allocate for the serialized packet

                  int packet_size = packet.Length;

 

                  bool isLittleEndian = BitConverter.IsLittleEndian;

 

                  UInt16 cksum = packet.i_cksum;

                  UInt16 id = packet.i_id;

                  UInt16 seq = packet.i_seq;

 

 

                  //allocate the byte array

                  byte[] packetArray = new byte[packet_size];

 

                  // now serialize the packet into the array.

                  int index = 0;

                  packetArray[index++] = packet.i_type;

                  packetArray[index++] = packet.i_code;

                  // the checksum is 16 bits.

                  byte[] temp = BitConverter.GetBytes(cksum);

                  // copy it into the packetArray at the required offset

                  Array.Copy(temp, 0, packetArray, index, temp.Length);

                  index += 2;

                  // similarly, copy the rest.

                  temp = BitConverter.GetBytes(id);

                  Array.Copy(temp, 0, packetArray, index, temp.Length);

                  index += 2;

                  // copy seq#

                  temp = BitConverter.GetBytes(seq);

                  Array.Copy(temp, 0, packetArray, index, temp.Length);

                  index += 2;

                  // copy payload

                  if (packet.PayloadLength > 0)

                  {

                        Array.Copy(packet.data, 0, packetArray, index, packet.PayloadLength);

                  }

 

                  return packetArray;

            }

      };

 

      class Program

      {

            static void Main(string[] args)

            {

                  //if (args.Length != 1)

                  //{

                  //    Usage();

                  //}

 

                  //if (0 == String.Compare(args[0], "/?", true, CultureInfo.InvariantCulture)

                  //|| 0 == String.Compare(args[0], "-h", true, CultureInfo.InvariantCulture)

                  //|| 0 == String.Compare(args[0], "-?", true, CultureInfo.InvariantCulture))

                  //{

                  //    Usage();

                  //}

 

                  byte [] data = new byte[32];

                  int j = 0;

                  for (int i = 0; i < 32; i++)

                  {

                        data[i] = (byte)((int)'a' + j);

                        j = (j + 1) % 23;

                  }

 

                  ICMP_PACKET packet = ICMP_PACKET.CreateRequestPacket(0x200, 0x9603, data);

 

                  byte[] serialized = packet.Serialize();

 

                  for (int i = 0; i < serialized.Length; i++)

                  {