Become a Columnist Microsoft Exchange Site Microsoft Support SiteMSDN Exchange Site

   

Subscribe to OutlookExchange
Anderson Patricio
Ann Mc Donough
Bob Spurzem
Brian Veal
Catherine Creary
Cherry Beado
Colin Janssen
Collins Timothy Mutesaria
Drew Nicholson
Fred Volking
Glen Scales
Goran Husman
Guy Thomas
Henrik Walther
Jason Sherry
Jayme Bowers
John Young
Joyce Tang
Justin Braun
Konstantin Zheludev
Kristina Waters
Kuang Zhang
Mahmoud Magdy
Martin Tuip
Michael Dong
Michele Deo
Mitch Tulloch
Nicolas Blank
Pavel Nagaev
Ragnar Harper
Ricardo Silva
Richard Wakeman
Russ Iuliano
Santhosh Hanumanthappa
Steve Bryant
Steve Craig
Todd Walker
Tracey J. Rosenblath
 
 

New mail event sink TAPI, SAPI example    Download Samples

The following code is a sample that uses an event sink attached to a mailbox that fires when a new mail arrives, The following samples are junk draw standard only, meaning they have had just enough development to get them to work (sort of).  The sink grabs the text body of a message separates the new text in a message if its a reply to an older message . It then writes the new message text out to a text file with some other information such as subject and sender and then adds an entry into an access database. The reason for using this method is twofold you don't really want to use TAPI and SAPI on your mail server and also you need some form of queuing engine if you receive multiple messages at once. Another script runs on the Calling PC which has the modem hooked up which monitors this database every minute, when a record is inserted into the database (by the event sink) the calling PC detects this and then starts a process using the TAPI dialing script to make a call to a number it retrieves from active directory and then opens the text file produced by the event sink and using SAPI speaks the contents down the phone line.

The Code

I've used a two step approach to firing this event that adds a layer of abstraction for the Exchange event sinks. The event sink code looks as follows, the script expects that the newmess.vbs script is located in a d:\scripts directory on the server.

<SCRIPT LANGUAGE="VBScript">

Sub ExStoreEvents_OnSave(pEventInfo, bstrURLItem, lFlags)
    
Dim DispEvtInfo
Dim WshShell
Stm = bstrURLItem
set WshShell = CreateObject("WScript.Shell")
strrun = WshShell.run ("d:\scripts\newmess.vbs " & stm)
set WshShell = nothing
End Sub

</SCRIPT>

newmess.vbs

This is the main block of code that is called by the event sink code, the front end of the code takes the file url of the new mail that caused the event to fire.  The mail is then opened up and the name of the mailbox and the received time is extracted from the email and then used as the name of the text file to store that saved text. This line highlighted in red refers to the save location of the text file and the value that will be entered into the database. This directory must be assessable from both the mail server and the calling PC.

on error resume next
set WshShell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
Dim WshShell, nfile, fso, msgobj, msgobj1
Set objArgs = WScript.Arguments
For I = 0 to objArgs.Count - 1
   if I = 0 then
	inbstr = objArgs(I)
   else
	inbstr = inbstr & " " & objArgs(I)
   end if
Next
inbstr = lcase(inbstr)
set WshShell = CreateObject("WScript.Shell")
Set msgobj = CreateObject("CDO.Message")
msgobj.DataSource.Open inbstr
uname = mid(inbstr,(instr(inbstr,"MBX")+4),(instr(inbstr,"Inbox") - (instr(inbstr,"MBX")+5)))
rtime = replace(msgobj.ReceivedTime,"/","-")
rtime = replace(rtime,":","-")
fname = "\\server\share\" & uname & rtime & ".txt"
fname1 = "\\server\share\" & uname & rtime & ".txt"

The next piece of code then parses the text body of the message and attempts to locate only the new text in a message if this message is a reply. It does this by finding lines within the text body that indicates that someone has responded to a message eg (---original message-----). For each different mail client this is different (even between different versions of outlook this is different) so I've tried to cater for as many permutations as possible. Once it has located the start of the reply message it can then parse out only the new content in the messages.

slen = 1
elen = 1
set wfile = fso.opentextfile(fname,8,true)
Do until stratend = 1
if instr(slen,msgobj.textbody,vbCrLf) then
	elen = instr(slen,msgobj.textbody,vbCrLf)
	elen1 = elen - slen
	comex = replace(mid(msgobj.textbody,slen,elen1),vbCrLf," ")
	if instr(comex,"-----Original Message-----") then
		stratend = 1
		elen = slen
	elseif instr(comex,"To:") then
		stratend = 1
		elen = slen
	elseif instr(comex,"Wrote:") then
		stratend = 1
		elen = slen
	elseif instr(comex,"From:") then
		stratend = 1
		elen = slen
	else
	end if
	slen = elen + 1
else
	stratend = 1
end if
loop

Some Introduction speech is then added to this and the whole lot is written to a text file which will be used by the TAPI script as the text to speak down the phone line.

wfile.writeline("Message From " & msgobj.fields("urn:schemas:httpmail:fromname"))
wfile.writeline("Subject " & msgobj.subject)
wfile.writeline(mid(msgobj.textbody,1,elen))
wfile.close
set msgobj = nothing

The last part of the code updates an Access database which acts as a queuing database on the TAPI calling PC. In the access database it stores the mailbox that called the code the date and time and the name and location of the text file that contains the text to speak.

Set Cnxn1 = CreateObject("ADODB.Connection")
strCnxn1 = "DSN=extapi"
Cnxn1.Open strCnxn1
sSql = "INSERT INTO Messages ( [Date], [Time], Username, filename ) values ('" & getdate1() & "','" & formatdatetime(now(),4) & "','" & uname & "','" & fname1 & "')"
Cnxn1.Execute(sSql)
Cnxn1.close
set Cnxn1 = nothing

Tapappv1.vbs

This is the script that runs on the calling PC that has the modem hooked up, using ADO it first opens up the database and checks to see if a call is already in process. If there is a record in the calling table then the call is in process.

Set Cnxn1 = CreateObject("ADODB.Connection")
strCnxn1 = "DSN=extapi"
Cnxn1.Open strCnxn1
SSQL = "Select * from calling"
set sqlQuery = CreateObject("ADODB.Recordset")
sqlQuery.CursorType = 3
sqlQuery.Open SSQL, Cnxn1, 3, 3
if sqlQuery.recordcount = 0 then

If a call is not in process the next thing it does is check if there are any new message to send (by querying the message table). If there are new messages to send it first inserts a record into the calling table to flag that a call is in process (this is to cater for overlapping scripts). The next thing the script does is using some ADSI code is to query active directory for the mobile phone number of the users mailbox who is going to be called.

		SSQL2 = "Insert into calling ([inuse]) values ('true')"
		Cnxn1.execute(SSQL2)
		DomainName = "yourdomain.com.au"
		Set oRoot = GetObject("LDAP://" & DomainName & "/rootDSE")
		strDefaultNamingContext = oRoot.get("defaultNamingContext")

		CUserID = sqlQuery1.fields("username")

		GALQueryFilter = "(&(&(&(& (mailnickname=*) (| (&(objectCategory=person)(objectClass=user)(!(homeMDB=*))(!(msExchHomeServerName=*)))(&(objectCategory=person)(objectClass=user)(|(homeMDB=*)(msExchHomeServerName=*))) )))(objectCategory=user)(samAccountName=" & CUserID & ")))"

		strQuery = "<LDAP://" & DomainName & "/" & strDefaultNamingContext & ">;" & GALQueryFilter & ";distinguishedName,displayName,mobile,msExchHideFromAddressLists;subtree"

		Set oConn = CreateObject("ADODB.Connection") 'Create an ADO Connection
		oConn.Provider = "ADsDSOOBJECT"              ' ADSI OLE-DB provider
		oConn.Open "ADs Provider"

		Set oComm = CreateObject("ADODB.Command") ' Create an ADO Command
		oComm.ActiveConnection = oConn
		oComm.Properties("Page Size") = 1000
		oComm.CommandText = strQuery

		Set rs = oComm.Execute

There is one hard-coded line in this code you have to change to get it to work in your environment which is the DomainName variable, this should be set to your active directory domain name.

The last part of the code shells out to the TAP2.vbs script and passes the phone number and name of the text file to use for the speech section. Once the tap2.vbs returns the next part of the code deletes the record that was just used and then sets the calling table back to indicate the call has been completed.

		cmdex = wscript.run("d:\scripts\tap2.vbs " & rs.fields("mobile") & " " & chr(34) & sqlQuery1.fields("filename") & chr(34),1,true)
		
		SSql3 = "DELETE calling.InUse FROM calling"
		Cnxn1.execute(SSQL3)
		sqlQuery1.delete 1
	end if
end if

One hardcoded line in this script expects the tap2.vbs script to be located in the d:\script directory

TAPI dialing Script TAP2.vbs

This is the TAPI dialing script which takes two command line parameters the first is the number to call and the second is the name of the text file that contains the text that you want to speak down the phone line. This script is based on the platform SDK calldemo.htm web page, what I've done with tap2.vbs is convert it from a web page to a console script put some command line argument code in to accept the phone number and a text file as command line augments and added a small delay routing and some SAPI code to allow the script to speak down the phone line. The changes I've made are as following

Set obArgs = WScript.Arguments
set TAPIOBJ = createobject("TAPI.TAPI.1")
set Mapper = createobject("DispatchMapper.DispatchMapper.1")
call TAPIOBJ.Initialize
sUnableToComplete = False
TAPIOBJ.EventFilter = &H1FFFF&
iAddressType = 1
pConnectTo = obArgs.Item(0)
Addressfrom = "D-Link FM-56SA-SCM V.90,K56Flex"

Call Connect(pConnectTo,iAddressType,addressFrom,true,true)
wscript.sleep 20000
if strconnect = "Connected" then
Set fso = CreateObject("Scripting.FileSystemObject")
        set nfile = fso.opentextfile(obArgs.Item(1),1,true)
	Do until nfile.AtEndOfStream = True
		inpline = inpline & nfile.readline & " "
	loop
	set spvoice = createobject("SAPI.SpVoice")
	spvoice.Speak inpline
end if
Call DisconnectCall(1)

The one line that is hardcoded is the addressform variable which contains the name of the modem that you are using you need to replace this variable with the name of the modem you are using on your calling PC. You can get this from the following registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ras\Tapi Devices\Unimodem\"Friendly Name". Once this is done the TAPI script is now ready to use. One note I left a lot of diag code in this script so if something does go wrong it should pop up a msgbox with some more information if there are no errors or the code fails to connect it should run through without putting up any dialogue boxes.

Installing and using the code

There are two steps you need to do to install and use this code the first is to setup the event sink on your mail server. The event sink code uses a system DSN which you need to create that points to the access database(sapitapi.mdb), the DSN name I've used in the code is EXTAPI. Because you want to share this database between the server and the calling PC you want to set the database location to a shared UNC path that will be accessible to the account that is running the Exoledbscript object and the calling PC. For details on using VBS Event sinks with Exchange see my previous article Using VBS Event Sink scripts with the Web Storage System. Or have a look in the ESDK search for regevent.vbs.

The second step to setting up this code is to set up the calling PC which is the PC you have hooked the modem up to and you have installed the SAPI SDK on which you can get from here. On the calling PC the one thing you must setup is a System DSN that points to the access database(sapitapi.mdb) that is referenced in the eventsink code the name of the DSN should be EXTAPI. Your PC also needs to have access to the shared drive where the text files that the event sink created are saved (this drive or UNC path must match the path that is stored in the access database). After you have setup the DSN you can then create a schedule task on the PC to run every minute (or whatever interval you want it to run at) to run the Tapappv1.vbs script. The other thing you need to do on the calling PC is setup the modem firstly you need to get an audio cable and hook the Mic socket to the speaker socket on the sound card of the PC. In the modem configuration turn the modem speaker on. (at command m2 on some modems)

As I said at the start of the article this code is junk drawer standard only  (so no guarantees that it will work) but there may be a few routines here that maybe helpfully for your other scripting projects. I've included a calendar sink example that works similar to this code with the exception that its designed to catch un-dismissed reminders and then call a mobile phone number it retrieves from active directory see this article for more details.

Download Samples


Disclaimer: Your use of the information contained in these pages is at your sole risk. All information on these pages is provided "as is", without any warranty, whether express or implied, of its accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the third-party products or information mentioned in the work are authored, recommended, supported or guaranteed by Stephen Bryant or Pro Exchange. OutlookExchange.Com, Stephen Bryant and Pro Exchange shall not be liable for any damages you may sustain by using this information, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages.

Copyright Stephen Bryant 2008