Dec 282013
 

I like building applications, and these days that means web applications. The challenge is enjoyable. During my vacation this year I decided to spend some time expanding some code that I’ve been working on over the years. In this case, a tool that runs on my PC that integrates with a database. Previously, I tended towards integrating with a local DB, be it via ODBC, a local text or Excel file, etc. This year I’m setting myself the challenge of integrating with a MySQL database over HTML.

The reason for this is because there is a substantial amount of information in my company’s online database (hosted by NetSuite), but getting access to it for any kind of automation can be tricky. However, it can be done if you’re willing to invest the development time to building an application (both sides of one). But, such a task is not something I’ve ever done before. So I’m combining a hobby and self improvement with the intention of building skills which may be useful at some point in the future at work.

As an example, an application which retrieves system information and helps organize and launch system connections (http, ssh, rdc) is a lot more portable if it retrieves the system information from a central database than something which requires all of the system information to be stored locally. If someone updates the system information and you don’t notice it, it means you could be attempting to connect to the wrong IP or server name. By automating the information retrieval you can save minutes (or over a calendar year, save hours or even days) worth of lookup time.

There are plenty of other uses, such as pulling information from a system, parsing it and then pushing it to a MySQL database directly, and then allowing that information to be displayed in an HTML format. Log parsing, etc., could be streamlined. Even, in one case, parsing the Microsoft hotfixes applied to servers and scrubbing it against a database for which hotfixes have been tested/approved by the manufacturer, would be a great application for my environment. Avaya already has something like that for the CS1000 that was created by the engineers back in the Nortel days. But they don’t have anything like that for the Avaya Aura Contact Center product line, even though they have the audit tool that would be necessary to implement the first half of that endeavor.

While tooling around with the XMLHTTP GET/POST integration for the client tool, I ran into an error on my website that was generated by Mod_Security. “An appropriate representation of the requested resource could not be found on this server. This error was generated by Mod_Security.

Upon further investigation, I managed to capture an error log out of the shared error log on my web host “ModSecurity: Access denied with code 406 (phase 2). Match of “rx ^0$” against “REQUEST_HEADERS:Content-Length” required. [file “/etc/httpd/modsecurity.d/10_asl_rules.conf”] [line “101”] [id “392301”] [rev “5”] [msg “Request Containing Content, but Missing Content-Type header”] [severity “NOTICE”]

The rest of the error is largely environment specific, so I’m omitting that info, but if you’ve run into this error yourself, you know what it looks like.

Here’s what I learned in my search (I’m effectively building a custom browser using Microsoft XMLHTTP— the mechanism isn’t too important, be it Power Shell, vbscript or jscript)

  1. Must declare RequestHeader User-Agent
  2. Must declare RequestHeader Content-Type
  3. For POST, must declare RequestHeader Content-Length

These are not mandatory for all HTML interactions, but some security configurations may require certain headers in order to process an XMLHTTP request. In the case of my web server (shared webhosting) and my custom browser application, for a GET request only the User-Agent and Content-Type were required. However, Mod_Security was configured on the shared webhost to mandate Content-Length.

What triggered this research and error was a typo in my code.

The typo came down to a bad choice in variable declaration. In a foreach ( item in array ) statement, I poorly chose the variables to be foreach ( item in items ) and, I’m sure you can see the typo risk already, I accidentally typed foreach ( item in item ). As such, the foreach loop did not properly iterate over the array… and since the foreach loop set the RequestHeaders, the request headers were not being set. Thus, the mod_security error.

I didn’t catch the typo initially, as the first error message was mostly meaningless and I couldn’t immediately determine the cause. Still, I saw a number of articles (including some wordpress blog support requests for this error, with some fixes involving changing the behavior of Mod_Security). While the Mod_Security error isn’t very meaningful, if you dig into the error_logs deep enough, you’ll find the error message which will lead you to the root cause of the problem. In my case, an HTTP 406 indicating that “Rquest Containing Content, but Missing Content-Type header.”

Once I found the typo in the code and fixed it, the XMLHTTP request worked perfectly (except that mod_security was also configured to require a content-length request header on POST requests, but once I’d fixed the one problem the other was easily to identify and fix.)

Apr 142013
 

 

Apr 132013
 

Frequently I find a need to do something with Windows, and VBScript is a flexible and fairly powerful language capable of doing all sorts of useful things. Granted there is JavaScript and PowerShell; and I should learn JavaScript more since there are several utilities that Avaya (formerly Nortel) has developed to collect data on systems and being able to write my own utilities to make myself more effective is a desirable skill.

I was writing a not-work-related utility this weekend and used several web sources to refresh my memory on how various VBScript functions, statements and operators worked. Here’s a list of some of those links:

Dim myArray(10)

myArray(0) = “Nothing to see, move along.”

Erase myArray

Mid( myString, 3, len(myString)-2 )

Replace( myString, “Find me”, “Replace me”, 1, -1)

Dim re

Set re = New RegExp

re.Pattern = “^We hold these truths.*”

re.IgnoreCase = true

re.Test( myString )

Dim re

Set re = New RegExp

re.Pattern = “These are the droids we”

re.IgnoreCase = true

myJediMindTrick = re.Replace(“These are the droids we are looking for”, “These are not the droids you”)

Dim myString

Dim myObject

Dim myInteger

myString = Empty

myObject = Nothing

myInteger = Null

‘ Although you could also use Empty on the integer variable.

UBound(myArray) returns the total number array elements (regardless of element values). isEmpty(myArray) returns false on arrays even if all array elements are empty.

Quick tip:

Const vbQuote = “”””

myString = vbQuote & “Quickly” & vbQuote & ” add quotation marks to any string.”

If Not booleanDroidsWeAreLookingFor Then

Call continueLooking

End if

On Error Resume Next

On Error Goto 0

I looked this up and discovered this really doesn’t do anything that I really want to happen. It would be nice if VBScript had a little bit more powerful error throwing/catching… sadly, it does not.

Mar 092013
 

Here are all of the trace messages I’ve been able to identify.

 

Acquire

Acquire_Resp

Answer

AnswerIndication

Answer_Resp

Application_Polling

CallAbandonedQueue

CallOffered

CallRouted

Release

ReleaseAcquire

ReleaseAcquire_Resp

ReleaseIndication

Release_Resp

RouteCall

RouteCall_Resp

RouteRequest

SF_StartRecord

SF_StartRecord_Resp

SF_StopRecord

SF_StopRecord_Resp

SFN_ActivityCode

SFN_Login

SFN_Logout

SFN_MakeSetBusy

SFN_MakeSetInService

SFN_NotReady

SFN_Ready

SFN_Return

SFN_StartRecord

SFN_StopRecord

SFN_WalkAway

StatusChange

 

  • An MLSM Trace message always begins:
    ff 0a 00
  • The fourth word is always the word-length of the message (including the three word header)
  • The fifth word is always the Associated ID
  • From there it gets a lot more complicated– I wish there was some documentation I could lay hands on.
    • 96 06 + 4 words is the Call ID which matches AML trace or Call By Call stats
    • 0e 00 02 + 10 words is the Calling Line ID associated with a call. Each digit in a CLID is broken out into a separate word ranging from 30 for 0 to 39 for 9. As an example, the number 877-555-1212 would be represented by 38 37 37 35 35 35 31 32 31 32.

I do have a breakdown of several CallRouted, RouteCall, RouteCall_Resp and RouteRequest messages, but unfortunately the pattern is consistant and I know for certain there are other call types (sources, numbering plans, etc.) that could be represented in the traces… I just don’t have examples of them.

It was kind of fun to write a parse utility to grab all of the clear-text headers and then write another procedure that would use those headers to collate all of the responses so that I could look for patterns. It’s similar to DCH traces. Given enough time I’m sure I can find out a number of obvious patterns within the hex codes… (like 96 06 and 0e 00 02… I’ve already noticed that oe 00 02 represents only one type of calling line ID. There are other hex patterns that might also include CLID information. Origination Type, Origination Address, Destination Type, Destination Address… there might be a few more.

Mar 072013
 

I’ve been dealing with a number of complicated cases recently for work requiring that I review mass quantity of multi-meg trace files (text files outputting raw log information). The reason for this is because the customer that I’m working with is using a CTI application (Chrysalis Cloudburst) to control call routing and perform screen-pop functionality at the agent’s desktop when the call arrives. As part of that process, Cloudburst accepts the call from the IVR application and then performs a CTI route via MLSM (Meridian Link Services Manager).

--------    Output from mlsm.exe        Tue Feb 19 09:13:43.781 2013   (13:43.781)
ITR Route
                  03 1f 00 00 00 00 1e 34 a5 8b 00 00    header (12 bytes)       
                                             95 01 05    Subtype (Route)      
                                    96 04 00 00 2f c1    Call Id       
                                          4b 02 66 26    CDN (6626)      
                                          31 02 66 a3    Terminating DN (6603)      
                                             67 01 00    Conditional (unconditional)      
=======================================================================================
--------    Input                       Tue Feb 19 09:13:43.781 2013   (13:43.796)
ITS Route
                  03 22 00 00 00 00 1e 35 a5 8b 00 00    header (12 bytes)       
                                             95 01 05    Subtype (Route)      
                                    96 04 00 00 2f c1    Call Id       
                                          4b 02 66 26    CDN (6626)      
                                          31 02 66 a3    Terminating DN (6603)      
                                             67 01 00    Conditional (unconditional)      
                                             aa 01 00    Status (Successful request)      
=======================================================================================

When you’re looking for twenty or thirty trace entries across three to twelve trace files, representing hundreds (or potentially thousands) of trace entries… it gets to be both time consuming and tedious. I decided to code a parse utility to make my life easier. Part that process is converting the decimal call ID (in the example above, Decimal 12225) to a hex call ID and properly formatting that hex value (hex 2fc1) so that it can be searched for in the trace files.

While I have no intention of releasing the full parse utility to the public (if you really want a copy, contact me via LinkedIn, or comment on this blog and we’ll discuss it), I thought it might be helpful for those “do it yourselfers” who might want a hint at how to overcome one of the challenges I had to overcome to get this utility functional.

I’m using VBScript running as a cscript:

'**
'* function convertCIDtoCallID integerCallID
'* returns stringFormattedHexCallID
'*
'* Converts a decimal call ID to a hex string call ID with spaces between each byte (for use in searching AML logs)
'*

function convertCIDtoCallID( iCID )
 ' convert decimal Call ID to hex, then convert to lowercase
 hCID = lcase(hex(iCID))
 sRetVal = ""

 ' if the number of characters in the hex Call ID is odd, then we need a leading 0 inserted
 if (len(hCID)%2)=1 then
  hCID = "0" & hCID
 end if

 ' for each character from 1 to the length of the hCID string
 for i=1 to len(hCID)
  ' group hex values in groups of two characters
  if ((i mod 2) = 1) and (i>2) then
   ' insert a space between hex value groups
   sRetVal = sRetVal & " "
  end if
  ' append the current character to the return value
  sRetVal = sRetVal & mid(hCID,i,1)
 next

 ' return the formatted hex call ID string
 convertCIDtoCallID = sRetVal

end function

What this does is convert a decimal value to hex (12225 to 2fc1) and then formats it the way it might appear in an AML trace (2f c1). It also automatically adjusts for scenarios where the hex value is 3 hex digits (hex fc1 = dec 4033) to something that can be searched for (0f c1) instead of mishandling (fc 1, which would not be found in the traces.)

This should save me hours in reviewing log files.

Jan 172013
 

As a continuation of the previous Tarot card simulation, I’ve been playing around with re-writing the Excel tool as a VBScript that I can execute from a (Windows 7) CMD line.

Function getrnd( low, high )
	getrnd = int((high-low+1)*rnd+low)
End Function

Function reverseCard( reverseState )
	if reverseState = 0 then
		reverseCard = 1
	else
		reverseCard = 0
	end if
End Function

Sub shuffleDeck()
	fdCurCard = 1
	lmax = getrnd(33,45) ' 39 +- 6
	rmax = 78 - lmax

	reverse = getrnd(0,1)

	for i = 1 to lmax
		daCards(i) = fdCards(i)
		daReversed(i) = reverseCard(fdReversed(i))
	next

	for i = 1 to rmax
		dbCards(i) = fdCards(i+lmax)
		dbReversed(i) = fdReversed(i+lmax)
	next

	lcnt = 1
	rcnt = 1
	fcnt = 1

	s = getrnd(0,1)

	do while fcnt<=78
 		r = getrnd(1,4)
 		if (s=0) then
 			do until r=0 or lcnt>lmax
				fdCards(fcnt) = daCards(lcnt)
				fdReversed(fcnt) = daReversed(lcnt)
				lcnt = lcnt+1
				fcnt = fcnt+1
				r = r-1
			loop
		else
			do until r=0 or rcnt>rmax
				fdCards(fcnt) = dbCards(rcnt)
				fdReversed(fcnt) = dbReversed(rcnt)
				rcnt = rcnt+1
				fcnt = fcnt+1
				r = r-1
			loop
		end if

		if (s=0) then
			s=1
		else
			s=0
		end if
	loop

End Sub

This assumes that you’ve already got a pre-populated deck. The challenge with this approach is (I’ve found) that the cards aren’t truly randomized at all. If you start with a deck that is not randomized at all (completely ordered, as if it just came out of the box), it takes a while for the randomization to start to occur. For instance, if the Fool is the top card, statistically speaking, that card should end up in the first 8-12 cards you pull… until you really randomize the deck.

The simplest approach to radomizing the deck that I could think of was to do a random card swap. Do a for-next loop through the entire deck and swap each card randomly with some other card in the deck. By the time you complete a pass through the deck (78 total card swaps) you have a fairly random deck setup and the shuffle algorithm simulates shuffling much better.

sub randomizeDeck()
	fdCurCard = 1

	for i = 1 to 78
		tcardnum = i
		do until tcardnumi
			tcardnum = getrnd(1,78)
		loop

		tcard = fdCards(i)
		trev = fdReversed(i)

		fdCards(i) = fdCards(tcardnum)
		fdReversed(i) = fdReversed(tcardnum)

		fdCards(tcardnum) = tcard
		fdReversed(tcardnum) = getrnd(0,1)
	next
end sub

Anyway… This is a work in progress.

Aug 162012
 

Lots of suggestions out there on scripting a mute toggle. In short:

Set WshShell = CreateObject("WScript.Shell")
WshShell.SendKeys(chr(&hAD))

While this looks really easy, there is something everyone forgot to mention for the Task Scheduler.

In order for this script to execute, it must be Run with the highest privileges.

 

Why am I interested in this? Well, my home office contains my work computer. Which is about 40 feet from my bedroom. The wife dislikes the chirping noise Microsoft Group Chat makes when people send messages during non-work hours. I sleep through them. She does not. Thusly, I needed a way to automate toggling mute. I wish it was context sensitive, because it would be nice to have a way to specifically mute/un-mute…