Open an SA web page directly to any object you want
Once you find an object in the database such as a subscriber or a call handler, you may just wish to fall back and punt to the Unity web based SA interface to allow your users to update the properties. This is actually a pretty good technique to use since it absolves you from having to do all that business logic legwork and deal with licensing issues and other headaches that the system administration console already has in place. We use this technique in a number of applications such as the Global Subscriber Manager, Audio Text Manager and dbWalker among others.
The idea is simple – the Unity SA web pages are designed such that you can open them up directly to the object you want without having to go through search dialogs, deal with frames or any of the things that can trip you up with other web interfaces. Armed with a unique ID for the object in question, the Unity home server name it lives on and the "formula" for constructing the URLs you can jump directly to just about any object in the database.
The unique ID in most cases is the "ObjectID" value for the table. For instance the CallHandler table would use the CallHandlerObjectID column as it's unique identifier. The exception to this is subscribers which use the DirectoryID column instead of it's SubscriberObjectID column as you would expect. The reason for this is because the original concept was to allow you to grab the DirectoryID value out of the directory (AD, Exchange 55 or Domino) and be able to jump to the web page for that user without having to access the database to find their ObjectID value. On paper this looks nifty. In reality you're missing one very important piece of information: the home Unity server name. You have to open the web page for the object you want to edit, including subscribers, on the Unity server that object is homed on. This server name is not stored in the directory on the subscriber itself, it's on the location object the user is associated with. Yes, the subscriber's directory object has a reference to the location object by ID and technically you could rummage through the directory, get all location objects and find the one you're looking for and pull the home server name off of it. While this is possible, it's far from easy. It's much easier to connect to a Unity server and get what you need out of SQL rather than doing things right out of the directory.
The "Generating SA Links" example on the sample page of www.CiscoUnityTools.com runs through how to create links to all object types, but here's the quick version. Assuming you have the home Unity server name stored in the variable strServerName and the CallHandlerObjectID stored in the strObjectID variable, to construct the URL that you could use to jump right to that call handler using this code:
strURL = "http://" & strServerName & "/Web/SA/FrameASP/handFrame.asp?id=%01%0703%3A" & transformObjectID(strObjectID)
The transormObjectID function is used to replace characters that the web server has problems with that are found in the ObjectId value. Being a GUID it contains "{", "}" and "-" characters, all which throw things off and, as such, need to be replaced with their "escape code" equivalents. This is very simple in VB, the function looks like this:
Public Function transformObjectID(strObjectID As String) As String
transformObjectID = strObjectID
transformObjectID = Replace(transformObjectID, "{", "%7B")
transformObjectID = Replace(transformObjectID, "}", "%7D")
transformObjectID = Replace(transformObjectID, "-", "%2D")
End Function
This same technique applies to all objects except subscribers. The only thing that changes from object to object is the .asp page name and the "preamble" to the object ID. For instance the interview handler link code would look very similar to the call handler code above but look carefully and you'll spot the differences:
strURL = "http://" & strServerName & "/Web/SA/FrameASP/IntHFrame.asp?id=%01%0705%3A" & transformObjectID(strObjectID)
For subscribers, however, it's a little simpler since the directory ID has no characters that need to be replaced with escape codes and there's no "preamble" to deal with. The code to construct the URL for a subscriber would look like this:
strURL = "http://" & strServerName & "/Web/SA/FrameASP/SubsFrame.asp?DirID=" & strDirectoryID
Even if you're planning on doing all your own administration interfaces down the road you should consider this technique to "bridge the gap" and provide users of your applications with fast, easy access to administration interfaces you have not yet developed.
Call Handler Information Dump
This example will walk through the process of dumping out the basic information about any call handler in the Unity server you're connected to. We'll deal with application call handlers here however this same technique would also work with primary call handlers associated with subscribers. You'd only have to modify the logic to take into account that subscribers can have alternate extensions and that subscribers always have their alternate contact rule enabled and the standard and off hours rules are never used.
This type of application is largely an exercise of knowing which tables contain what information related to call handlers. Since much of this was covered in the Data Object Model chapter I'm not going to spend too much time on this here. There are a couple of important points you'd do well to cover if you're building a reporting or database "dump" type application in general.
Many places in the Unity database you'll see a trilogy of properties that indicate where a caller should be sent. These are an "Action", "Conversation Name" and "Destination Object ID" set that can be found in varying forms in dozens of places throughout the database. If you pop open UnityDB and wander around you'll find these for user input keys, messaging rules, on call handlers for after message actions, on subscribers for exit actions, name lookup handler exits, interview handler exits etc. Since they come up so often, if you're building a reporting type application such as this you'll definitely want to construct a generic routine that can convert these values into a human readable text stream you can reference in your output. The following routine is one used in the Call Handler Information Dump sample code for this purpose:
Public Function strConstructActionString(iAction As Integer, strConversationName As String, strObjectID As String) As String
Dim strAction As String
Dim strSQL As String
Dim rs As New ADODB.Recordset
Select Case iAction
Case 0
'an ignore action is only found for user input keys
strAction = "Ignore"
Case 1
strAction = "Hangup"
Case 2
'An action of 2 is the generic "go to" action which is by far and away the most common action value you'll encounter. This is used for sending callers to any other object in the system which is, of course, something that happens frequently.
If (StrComp(strConversationName, "chInterview", vbTextCompare) = 0) Then
'The chInterview conversation means the conversation is going to send the caller to an interview handler here so there should be the ObjectID of an interviewer in the ObjectId parameter here.
strSQL = "SELECT TextName FROM vw_InterviewHandler WHERE InterviewHandlerObjectID='" + strObjectID + "'"
rs.Open strSQL, strConnectionString, adOpenKeyset, adLockReadOnly
If rs.RecordCount = 0 Then
strAction = "(error) Send caller to missing interview handler. ObjectId=" + strObjectID
Else
rs.MoveFirst
strAction = "Send caller to interview handler: " + rs("TextName")
End If
ElseIf ((StrComp(strConversationName, "PHGreeting", vbTextCompare) = 0) Or (StrComp(strConversationName, "PHTransfer", vbTextCompare) = 0)) Then
'The PHGreeting or PHTransfer conversation means the conversation is sending the caller to another call handler - either to the transfer rules entry point (PHTransfer) or skipping the transfer rules and going right to the greetings (PHGreeting). Remember that a call handler can also be marked primary which means it's part of a subscriber so we need to take that into account here.
If StrComp(strConversationName, "PHGreeting", vbTextCompare = 0) Then
strAction = "Send caller to greeting for "
Else
strAction = "Send caller to transfer for "
End If
strSQL = "SELECT TextName, CallHandlerObjectID, IsPrimary FROM vw_CallHandler WHERE CallHandlerObjectID='" + strObjectID + "'"
rs.Open strSQL, strConnectionString, adOpenKeyset, adLockReadOnly
If rs.RecordCount = 0 Then
strAction = "(error) Send caller to missing call handler. ObjectId=" + strObjectID
Else
rs.MoveFirst
If rs("IsPrimary") = 1 Then
'it's a primary call handler which means it's a subscriber we're sending the caller to. Adjust the action string accordingly
strSQL = "SELECT DisplayName FROM vw_Subscriber WHERE CallHandlerObjectID='" + rs("CallHandlerOjectID") + "'"
rs.Close
rs.Open strSQL, strConnectionString, adOpenKeyset, adLockReadOnly
If rs.RecordCount = 0 Then
strAction = strAction + " subscriber - (error) could not find subscriber that owns this primary call handler."
Else
strAction = strAction + " subscriber with display name = " + rs("DisplayName")
End If
Else
'it's just an ordinary application call handler
strAction = strAction + " call handler: " + rs("TextName")
End If
End If
ElseIf StrComp(strConversationName, "AD", vbTextCompare) = 0 Then
'send the caller to a name lookup handler (or alpha directory, hence the "AD" conversation name there) - there should be an ObjectID of a name lookup handler in the ObjectID parameter.
strSQL = "SELECT TextName FROM vw_NameLookupHandler WHERE NameLookupHandlerObjectID='" + strObjectID + "'"
rs.Open strSQL, strConnectionString, adOpenKeyset, adLockReadOnly
If rs.RecordCount = 0 Then
strAction = "(error) Send caller to missing name lookup handler. ObjectId=" + strObjectID
Else
rs.MoveFirst
strAction = "Send caller to name lookup handler: " + rs("TextName")
End If
ElseIf StrComp(strConversationName, "SubSignIn", vbTextCompare) = 0 Then
'The subscriber sign in conversation doesn't take a destination object ID, it's a stand alone "routable" conversation that you can just launch directly.
strAction = "Subscriber sign in"
ElseIf StrComp(strConversationName, "GreetingsAdministrator", vbTextCompare) = 0 Then
'The greetings administration conversation also doesn't take a destination object ID, you just send the call to the conversation directly.
strAction = "Greetings administration"
Else
MsgBox "Error! Unknown conversation string passed to strConstructActionString: " + strConversationName
Exit Function
End If
Case 4
'The take message option is only available in the user input table
strAction = "Take Message"
Case 5
'The skip greeting action is also only available in the user input table.
strAction = "Skip Greeting"
Case Else
'bad action value
strAction = "Error in strConstructActionString - Invalid action value passed in:"+str(iAction)
End Select
StrConstructActionString = strAction
End Sub
With your "action string" routine in place the rest of the call handler information dump work is actually pretty straight forward stuff. You can look in CUDLE and decide which high level information you want off your call handler object itself and then walk through the related collections. The following code chunks assume you already have the rsCallHandler recordset open and pointing to the row with the call handler you're interested in.
To get all the transfer rules associated with a call handler you'd want to use this query:
RsTemp.Open "SELECT * FROM vw_ContactRule WHERE vw_ContactRule.ParentObjectID='"+rsCallHandlers("CallHandlerObjectID")+"'", strConnectionString, adOpenKeyset, adLockReadOnly
This should result in a recordset with 3 rows in it: the standard, off hours and alternate transfer rules for the call handler. You can decide what information you want to gather and report on for each contact rule, however remember that like messaging rules, contact rules are enabled and disabled by using the TimeExpires column. A time in the future or set to NULL means the rule is active, a time in the past means it's disabled. The standard contact rule should never be disabled, even for primary call handlers where it's not used since the alternate is always enabled and over riding it. Other information on a contact rule that would be interesting are if it's going to try and ring a phone or not which is determined by it's "action" column: 0 means no transfer will take place and a 1 means the transfer will be attempted. If a transfer is enabled you'll also be interested in reporting which transfer string is going to be used which is stored in the "Extension" column. There's also information about the number of rings, the transfer type, what we do when it's busy and which switch (in the case of a dual switch integration) the contact rule is associated with. Again, a trip through the ContactRule table in CUDLE will help you decide which items you want to include in your report output.
To get all the greetings associated with a call handler, you'd want to use this query
RsTemp.Open "SELECT * FROM vw_MessagingRule WHERE vw_MessagingRule.ParentObjectID='"+rsCallHandlers("CallHandlerObjectID")+"'", strConnectionString, adOpenKeyset, adLockReadOnly
This should result in a recordset with 6 rows in it: the standard, off hours, alternate, busy, internal and error greetings associated with the call handler. Items of interest in the greeting rows will be the "Action", "ConversationName" and "DestinationObjectID" set that determine what will happen with the call after the greeting plays as described above. You'll also be interested in the "PlayWhat" value which determines if Unity will play the canned system greeting (0), a custom recorded greeting (1) or a blank greeting (2). There's a number of other settings such as how many times the greeting will replay before taking the after greeting action, the path to the WAV file for a custom recorded greeting (if any), if digits are being ignored during this greeting or not etc. Again, you can look through the table itself in CUDLE and decide which properties you want to report on. The only tricky bit is checking to see if the greeting rule is enabled or not using the TimeExpires column as described with the contact rules above.
To get all user input keys defined for a call handler, you'd use this query:
RsTemp.Open "SELECT * FROM vw_MenuEntry WHERE vw_MenuEntry.ParentObjectID='"+rsCallHandlers("CallHandlerObjectID")+"'", strConnectionString, adOpenKeyset, adLockReadOnly
This should result in a recordset with 12 rows in it: one each for the keys 0-9, * and #. The primary information about a one key rule is what it's action values indicate will happen when the user presses that key while a greeting is playing for it's parent call handler. The action string routine we built above will glean this information nicely for you using the "Action", "ConversationName", and "DestinationObjectID" columns. The only other piece of information on a user input key row that would be of interest is if the key is locked or not.
There are a number of other references in the call handler table to other tables that you should be able to figure out how to deal with fairly easily. The LocationObjectID references a row in the Location table, the ScheduleObjectName references a row in the Schedule table, and there's a set of "action values" for the After Message action as discussed above. The AdministatorObjectID and RecipientObjectID, however, can reference either a subscriber or a public distribution list. You have to check the AdministratorObjectIDType and RecipientObjectIDType values to know which table to go look the ObjectID value up in. A value of "1" means it's a subscriber and a value of "2" means it's a public distribution list.
The only other piece of information on a call handler that needs special attention is the language it's assigned to. These are stored as four digit Windows language codes. For instance US English is language ID 1033. To convert them into human readable strings you'll want to use a WinAPI call to do this. In VB you have to jump through a couple hoops to make this work since the language doesn't have "pointers" in the C, C++ sense of the word so you need to wrap the function to make it usable. The following code chunk can be found in the Call Handler Information Dump example as well:
'WinAPI call as defined in VB to convert a language code into a language name string
Private Declare Function VerLanguageName& Lib "version.dll" Alias "VerLanguageNameA" (ByVal wLang As Long, ByVal szLang As String, ByVal nSize As Long)
'VB function that calls the above WinAPI function and passes back a usable string to the calling routine. The string returned from the strLanguageIDToFullName should be a full language description string that looks like "English (United States)".
Public Function strLanguageIDToFullName(lLanguageID As Long) As String
Dim strBuffer As String
Dim rt As Long
'You need to preallocate space into the string and pass the string variable into the WinAPI call to get the language name - this is how VB "simulates" pointers for the purpose of dealing with the Win32 interface here.
strBuffer = String$(256, vbNullChar)
'rt gets the number of characters read into the buffer
rt = VerLanguageName(lLanguageID, strBuffer, Len(strBuffer))
If rt > 0 Then
strLanguageIDToFullName = Left(strBuffer, rt)
Else
strLanguageIDToFullName = "(error) No language found for ID=" + Str(lLanguageID)
End If
End Function
We'll talk briefly about how to get at subscriber information for similar reporting type needs in the next section.
ไม่มีความคิดเห็น:
แสดงความคิดเห็น