Programming MicrosoftВ® OutlookВ® and Microsoft Exchange 2003, Third Edition (Pro-Developer)
Now that you know how to successfully log on to the Exchange Server, let's take a look at our first sample application, the Helpdesk application. This is a Web-based application that allows users to submit new help requests . Helpdesk technicians can, in turn , use their Web browser to view and answer help requests as well as schedule meetings to go on site to solve the users' problems. The technicians can use different views for the help tickets stored in the system to quickly see who the ticket is from, when it was sent, and its status. (A help ticket for the Helpdesk application is shown in Figure 11-2.) When browsing help tickets, the technicians are presented with machine configuration information from a Microsoft Access database, which makes it easier for them to determine whether the issue is related to hardware, software, or a user error.
This application is the most complex of all the sample applications in this book, but the code is easy to follow and shows you how to use many of the objects in the CDO library. There are actually two versions of the application among the book's sample files. The version you use will depend on the Web browser you want to target. One version implements a user interface for the help tickets by using Dynamic HTML (DHTML). The other version uses HTML tables. A screen from the non-DHTML version is shown in Figure 11-3.
Setting Up the Application
Before you can install the application, you must have a Windows 2000 or later server and a client with certain software installed. Table 11-2 outlines the installation requirements.
Required Software | Installation Notes |
---|---|
Exchange Server 5.5 with the latest service pack or Exchange 2000/2003 with the latest service pack | OWA must be installed for Exchange 5.5. Exchange 2003 installs OWA by default. |
IIS 3.0 or later with ASP | IIS 5.0 is recommended. |
CDO library (cdo.dll) and CDO Rendering library | Exchange Server installs CDO 1.21. For CDO Rendering on a clean Exchange 2003 install, you must install OWA from Exchange 5.5 on the machine to get CDOHTML. |
ActiveX Data Objects | |
Access 97 or later (optional) | Install Access if you want to use the database access feature. Access 2000 or beyond is recommended. |
For the client: A Web browser or Outlook |
For the Web browser, Internet Explorer 5.0 or later is recommended. You can run the client software on the same machine or on a separate machine. |
To use the Helpdesk application, you must have some e-mail users. You can either select currently set up e-mail users or add new ones using the Exchange Administrator program. Be sure to fill in the directory information for your users; this information should include their office locations, phone numbers , titles, and departments. The Helpdesk application dynamically retrieves this information using the CDO library. If the information is not available in the directory, the application displays the text "None specified" for these fields.
To install the application, copy the Helpdesk folder from the companion content to the Web server where you want to run the application. If you are going to use a browser that does not support DHTML, such as Microsoft Internet Explorer 3.0, copy the three .asp files from the Nondhtml subfolder to the Helpdesk folder, overwriting the current files. These files will replace the DHTML versions of the Helpdesk with the HTML versions.
Start the IIS administration program. The user interface you see will depend on what version of IIS you have. Create a virtual directory that points to the location where you copied the helpdesk files, and then name the virtual directory helpdesk . You can use the URL http://<yourservername>/helpdesk to access your helpdesk.
Included with the helpdesk files is a sample Access database (smsdata.mdb) that allows the application to query for system information about the current user. To use this database, you must set up a system Data Source Name (DSN) for it on the server machine by using the Data Sources (ODBC) administrative tool in Control Panel. Point the system DSN at the smsdata.mdb file, and name the DSN Helpdesk . Open smsdata.mdb in Access, and edit the UserID fields for all three tables to reflect the display names of the users you have in Exchange.
Launch Outlook. (You can launch it on any machine because you will create a public folder for the help tickets; the only requirement is that the Exchange server with which the IIS server communicates can access that public folder.) Create a new public folder under All Public Folders. Name the folder Helpdesk , and select Task Items as the default item type for the folder.
Note | You must install the Helpdesk folder under All Public Folders or the application will not work. If you cannot install the application there, you can modify the code contained in the Helpdesk application so it looks for the folder in another location, or you can retrieve the folder by using its EntryID. You can use a tool such as MDBVUE (which is included with Exchange Server) to look up the EntryID of a folder. |
Among the Helpdesk files, you will find a file named helpdesk.fdm. This is a form definition file that you will use to import the correct fields needed by the Helpdesk application. In this case, the form is a Help Request task form with multiple custom fields.
To install the Help Request form, you must import this file into the Helpdesk public folder. To do this, right-click on the Helpdesk public folder and choose Properties. Click on the Forms tab, and click Manage to display the Forms Manager dialog box, as shown in Figure 11-4.
In the Forms Manager dialog box, click Install to display the Open dialog box. Select the All Files option from the Files Of Type drop-down list so Outlook does not search only for files with a .cfg extension. Locate and double-click on the helpdesk.fdm file to display the Form Properties dialog box. Click Cancel, and then click Close and OK. When you have the Helpdesk public folder selected, you should see a new item on the Actions menu named New Helpdesk Request.
You must create some views in the Outlook client that will be available to the Web browser client. This is one of the powerful features of CDO. These views will include some of the custom fields from the Help Request form you just installed. Using the Define Views dialog box and the information in Table 11-3, create the helpdesk, from, and status views for the Helpdesk public folder. (For information about creating views, see the section titled "Views" in Chapter 3.)
View | Type | Fields | Group By |
---|---|---|---|
Helpdesk | Table | Flag, From User (select from Help Request form), Received, Subject (select from All Mail Fields) | None |
From | Table | Flag (select from Help Request form), Received, Subject (select from All Mail Fields) | From User, in ascending order |
Status | Table | From User (select from Help Request form), Received, Subject (select from All Mail Fields) | Flag, in ascending order |
Using the Properties dialog box in Outlook for the Helpdesk public folder, set the permissions for your users. Give regular users who will submit help tickets Create Items permission. Give technicians who can submit, view, and resolve help tickets Create Items and Read Items permissions. Figure 11-5 shows a sample permissions setup for the Helpdesk folder. As you will see, the application checks the permissions of the current user for the Helpdesk folder to determine whether the user is a technician or just a regular user. If you do not give yourself at least Read Items permissions, you will not be able to see the menu item View Current Help Tickets in the Helpdesk application.
You're finished setting up the application. Try connecting your browser to http://<yourservername>/helpdesk to access the application.
Helpdesk CDO Session Considerations
You must be aware of certain issues when you build ASP-based applications with CDO (like the Helpdesk application). Recall from Chapter 10 that the ASP Session object, which is created when a new user connects to your Web application, maintains the user state for Web applications. ASP, in turn, runs the Session_OnStart subroutine in the Global.asa file. When a user's session times out or the user abandons the session, ASP runs the Session_OnEnd subroutine in the Global.asa file. This might seem simple enough, but the most common problem developers run into with CDO applications is that they don't put the correct code in the Session_OnStart and Session_OnEnd subroutines in the Global.asa. If you do not put the correct code in these subroutines, you might get an ASP 0115 error, which indicates that a trappable error has occurred in an external object. Your ASP application will cease working until you restart the Web server. To help you better understand what you need to do as a CDO developer, and to help you avoid this error in your application, let me explain in more detail exactly what happens when a user logs on and off an Exchange server in a CDO ASP application. I'll do this by showing you the Global.asa file from the Helpdesk application:
<SCRIPT LANGUAGE=VBScript RUNAT=Server> Sub Application_OnStart On Error Resume Next Set objRenderApp = Server.CreateObject("AMHTML.Application") If Err = 0 Then Set Application("RenderApplication") = objRenderApp Else Application("startupFatal") = Err.Number Application("startupFatalDescription") = _ "Failed to create application object<br>" & _ Err.Description End If Application("hImp") = 0 'Load the configuration information from the registry objRenderApp.LoadConfiguration 1, _ "HKEY_LOCAL_MACHINE\System\CurrentControlSet\" & _ "Services\MSExchangeWeb\Parameters" Application("ServerName") = objRenderApp.ConfigParameter("Server") Err.Clear End Sub Sub Application_OnEnd Set Application("RenderApplication") = Nothing End Sub Sub Session_OnStart On Error Resume Next 'This is a handle to the security context. 'It will be set to the correct value when a 'CDO session is created. Session("hImp") = 0 Set Session("AMSession") = Nothing End Sub 'While calling the Session_OnEnd event, IIS doesn't call us in 'the right security context. 'Workaround: current security context is stored in Session 'object and then gets restored in Session_OnEnd event handler. ' 'All CDO and CDO Rendering library objects stored in the 'Session object need to be explicitly set to Nothing between 'the two objRenderApp.Impersonate calls below. Sub Session_OnEnd On Error Resume Next set objRenderApp = Application("RenderApplication") fRevert = FALSE hImp = Session("hImp") If Not IsEmpty(hImp) Then fRevert = objRenderApp.Impersonate(hImp) End If 'Do our cleanup. Set all CDO and CDOHTML objects inside 'the session to Nothing. 'The CDO session is a little special because we need to do 'the Logoff on it. Set objOMSession = Session("AMSession") If Not objOMSession Is Nothing Then Set Session("AMSession") = Nothing objOMSession.Logoff Set objOMSession = Nothing End If If (fRevert) Then objRenderApp.Impersonate(0) End If End Sub </SCRIPT>
Note | If you are running a clean install of Exchange 2000 or 2003 and have not upgraded your machine from Exchange 5.5, some of the techniques shown here will not work because Exchange 2000 does not create certain registry entries in the same way as Exchange 5.5. For example, retrieving the server name using the RenderingApplication object's ConfigParameter code will not work. You can get around this by using the Window Scripting Host Network object's ComputerName property or some other method. |
The Application_OnStart subroutine is run only once, when the first user connects to the application. As you can see in the code, the first step is to create a new Application object from the CDO Rendering library. The ProgID for the CDO Rendering Application object is AMHTML.Application . The Application object is stored in an application-level variable so you can avoid having to create multiple AMHTML objects. Your application will perform better if you create the AMHTML object once and then use it throughout all user sessions.
Note | CDO was formerly named Active Messaging, so you will see both CDO and AM used throughout the CDO library. Consider any objects that are prefixed with AM to be CDO objects. |
The Application_OnStart subroutine initializes some variables and then uses a method of the Application object to retrieve the name of the Exchange server from the Web server's registry. This method allows portability of the code. You don't have to hardcode the name of the Exchange server; the Helpdesk application pulls the information from the registry. If you do not want to use the registry and instead want to retrieve the server information using code, you can use the Windows Scripting Host Network object:
Set wshNetwork = Server.CreateObject("WScript.Network") MsgBox "Computer Name = " & wshNetwork.ComputerName
After the first user connects and Application_OnStart is finished running, ASP runs the Session_OnStart subroutine for all users. Session_OnStart clears a session variable named hImp , which is a handle to the security context for the current user. Remember that when a user first browses a Web page in IIS, she is running in the security context of the IUSR_ servername account. This account is useful for browsing Web pages anonymously, but it is not useful for accessing Exchange Server objects and information because it has no implicit permissions on Exchange Server objects. So when you build an authenticated CDO application, you must force IIS to challenge the current user for her Microsoft Windows credentials. IIS and CDO can use the Windows security context for that user to attempt a logon to the Exchange server. The following code, taken from the Logon.inc file of the Helpdesk application, checks the http variables to make sure the current user is authenticated by the Web server using the user's Windows credentials. In the Helpdesk application, this section of code is called by every ASP page, just in case the user's ASP session has timed out.
Public Function BAuthenticateUser On Error Resume Next 'Response.Write("In BAuthenticateUser<br>") BAuthenticateUser = False bstrAT = Request.ServerVariables("AUTH_TYPE") vbTextCompare = 1 bstrAuthTypesSupported = "BasicNTLMDPAMBS_BASICNegotiate" If (InStr(1, bstrAuthTypesSupported, bstrAT, vbTextCompare) < 2) Then Response.Buffer = TRUE Response.Status = ("401 Unauthorized") Response.AddHeader "WWW.Authenticate", "Basic" Response.End Else BAuthenticateUser = True End If End Function
This function searches the AUTH_TYPE server variable to see whether Basic , NTLM , or Negotiate is contained anywhere in the string. If none of these is found, the user is unauthenticated and the script sends back a "401 Unauthorized" response and a header that will force the browser to challenge the user for credentials. Note that Negotiate was introduced with Windows 2000 and Internet Explorer 5.0. It allows the client to negotiate the authentication that it will use with the server.
Once the user is authenticated, you must save her security context as a Session variable. You must do this because IIS uses multiple threads of execution and you cannot be guaranteed that the thread that tries to destroy the session when the user logs off will be the same thread used to create the session. In the Session_OnEnd subroutine, the script checks to see whether hImp is not empty, which would imply that it is a valid handle. If hImp is not empty, hImp is used to specify the Windows NT security context to impersonate. Once the CDO and CDO Rendering objects are set to Nothing and the session is logged off, the CDO Rendering Application object reverts from the authenticated thread to the unauthenticated thread by calling the Impersonate method, with as the parameter.
As we step through the Helpdesk application, you will see that every page in the application checks to see whether the ASP session, and therefore the CDO session, has been abandoned or has timed out. If the session has been abandoned or has timed out, the application redirects the user to the logon page so she can restart her ASP and CDO sessions. Figure 11-6 shows the logon page.
Logging On to the Helpdesk
You've seen that the Global.asa and Logon.inc files both help to authenticate users and maintain sessions. However, we have not yet discussed how you actually use the Logon method of the CDO Session object in the Helpdesk application to create a valid session with the Exchange server. The Logon method must be called before you attempt to access any other CDO objects. The script that implements user logons is contained in the Logon.inc file, shown here:
<% 'logon.inc. VBScript methods to create and check an 'ActiveMessaging session '============================ 'ReportError '============================ Function ReportError(bstrContext) ReportError= False If Err.Number <> 0 Then Response.Write( "Error: " & bstrContext & " : " & _ Err.Number & ": " & Err.Description & "<br>") Err.Clear ReportError= True End If End Function '============================ 'BauthenticateUser ' 'Ensures user is authenticated. Note that this implies that 'Basic authentication is enabled on the IIS server. '============================ Public Function BAuthenticateUser On Error Resume Next 'Response.Write("In BAuthenticateUser<br>") BAuthenticateUser = False bstrAT = Request.ServerVariables("AUTH_TYPE") vbTextCompare = 1 bstrAuthTypesSupported = "_BasicNTLMDPAMBS_BASICNegotiate" If (InStr(1, bstrAuthTypesSupported, bstrAT, vbTextCompare) < 2) Then Response.Buffer = TRUE Response.Status = ("401 Unauthorized") Response.AddHeader "WWW.Authenticate", "Basic" Response.End Else BAuthenticateUser = True End If End Function '============================ 'CheckAMSession ' 'Checks for and returns the AM session in the Session object. 'If not found, calls NoSession. 'Call this before emitting any HTML or any redirects, 'authentication, and so on. 'Returns True if session exists or can be created. '============================ Public Function CheckAMSession() On Error Resume Next 'Response.Write("In GetSession<br>") Dim amSession CheckAMSession= False Set amSession= Session("AMSession") If amSession Is Nothing Then NoSession amSession= Session("AMSession") End If If Not amSession Is Nothing Then CheckAMSession = True End If End Function '============================ 'NoSession ' 'Called when the AM session can not be found. 'Either creates a session or gets more info from the user. 'Returns only if the session was created. '============================ Sub NoSession() On Error Resume Next Dim bstrMailbox Dim bstrServer Dim bstrProfileInfo Dim objAMSession1 Dim objRenderApp Dim objInbox bstrServer = "" bstrServer = Application("ServerName") 'REPLACE "MYSERVERNAME" WITH YOUR ACTUAL SERVER NAME 'bstrServer = "MYSERVERNAME" If bstrServer = "" Then response.write "Server Name is empty. Please check logon.inc " & _ "and replace the line 'bstrServerName = ""MYSERVERNAME"" " & _ "with your Exchange server name and uncomment the line. " & _ "For example, if your server's name is SERVER then this " & _ "line should read bstrServer = ""Server""" response.end Else If Session("mailbox") Is Nothing Then bstrMailbox = Request.QueryString("Contact_Email") Else bstrMailbox = Session("mailbox") End If 'must be authenticated to successfully logon BAuthenticateUser Err.Clear Set objAMSession1 = Server.CreateObject("MAPI.Session") If Not ReportError("create MAPI.Session") Then Set objRenderApp = Application("RenderApplication") 'Construct the ActiveMessaging profile from the 'server and mailbox name bstrProfileInfo = bstrServer + vbLf + bstrMailbox Err.Clear objAMSession1.Logon "", "", False, True, 0, True, _ bstrProfileInfo If Not ReportError( "CDO Logon") Then 'To ensure that we are really logged on, 'we need to try retrieving some data. Err.Clear Set objInbox = objAMSession1.Inbox 'The logon is no good. We'll do a little cleanup here. If Err.number <> 0 Then objAMSession1.Logoff Set objAMSession1 = Nothing %> <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=email.asp"; TARGET="_top"> <% 'Response.Redirect "default.htm" End If 'This will be retrieved back up in CheckSession 'Note that if the logon failed this is set to 'Nothing' Set Session("AMSession") = objAMSession1 'Need this to recreate the proper security 'context in Session_OnEnd Session("hImp") = objRenderApp.ImpID End If 'objAMSession1.Logon End If 'Server.CreateObject() End If 'bstrServerName End Sub
The first function called on every ASP page in the Helpdesk application is the CheckAMSession function, which checks to see whether the user already has a valid CDO Session object with the Exchange server. If this function does not find a valid object, it calls the NoSession subroutine to log the user on to CDO.
In the NoSession subroutine, the variable bstrServer is set to the Exchange server name, which is pulled from the registry in the Global.asa file. (Again, retrieving the Exchange server name from the registry allows the code to be ported to any Web server or Exchange server.) If the script cannot find the server name, it will return an error to the user. The script then checks to see whether the ASP Session variable named mailbox contains a valid mailbox alias name. If this variable is empty, the script attempts to grab the mailbox name from the URL that was passed to the Web server by the logon screen of the application.
The BAuthenticateUser function is called to ensure that the user is logged on to Windows NT correctly before the code attempts the CDO Session Logon method. After the CDO object is created, its Logon method is called. The variable bstrProfileInfo is set so CDO can dynamically create a profile for the user. The script calls the Logon method, and if that method returns no errors, the script tries to retrieve the Inbox for the user. You should follow similar steps in your applications because the Logon method can return successfully when called with dynamic profiles, even when the server or mailbox name is not valid. If you do not try to retrieve information from the server directly and assume that the Session object is valid because the method returned successfully, you will run into many problems.
If the code cannot retrieve the user's Inbox, it logs off and redirects the user to the logon page. If it can retrieve the Inbox, it stores the handle to the security context for this user in the hImp session variable.
Accessing Folders in the Helpdesk
You don't have much of a Helpdesk application if your users can't enter information and your technicians can't retrieve information. This Helpdesk application uses a public folder to store and retrieve information about ticket status. CDO provides access to public folders through its InfoStores collection, which contains all the different stores or databases that CDO can access. For example, with the InfoStores collection, you can access not only public folders and server-based mailboxes but also personal folders stored in .pst files or offline replicated folders stored in .ost files. Of course, to enable CDO to access some of these infostores, you must run your CDO application on the client machine. Figure 11-7 shows the InfoStores collection with some of its child objects.
In the Helpdesk application, the InfoStores collection is used to find the public folder store. The following code from default.asp uses a For Each Next statement to loop through the InfoStores collection and retrieve each store in the collection. Each store is checked to see whether the store name corresponds to the public folder store. If a corresponding store name is found, the For...Each statement is exited.
Set objInfoStoresColl = objOMSession.InfoStores For Each objInfoStore In objInfoStoresColl If objInfoStore.Name = "Public Folders" Then Exit For End If Next
Now that the application has the correct infostore, the correct set of folders must be accessed in that infostore . The InfoStore object in CDO has a RootFolder property that returns a Folder object representing the root of all the folders. For your mailbox and public folder stores, the RootFolder property returns a Folder object named IPM_Subtree . By using that Folder object, you can traverse all folders in your mailbox or top-level public folders. However, there's one caveat with this property: you cannot use the RootFolder property to access public folders if your application is running as a Windows NT service. Instead, you must use the Fields collection on the InfoStores object with the specific property tag PR_IPM_PUBLIC_FOLDERS_ENTRYID ( &H66310102 ). Once you retrieve the EntryID for the root public folder from the Fields collection, you can use the GetFolder method to actually retrieve the root public folder. (An EntryID is like a globally unique identifier, or GUID, in that it uniquely identifies the folder in the Exchange Server database.) After retrieving the root folder, you can use the Folders collection of the root public folder to retrieve the root folder's subfolders . The following code, taken from default.asp, shows the GetFolder method in action. It retrieves the folders in the public folder tree and then iterates through the top-level folders as it searches for the Helpdesk folder.
'This is the EntryID for the root public folder bstrPublicRootID = objInfoStore.Fields.Item(&H66310102).Value Set myrootfolder = objOMSession.GetFolder(bstrPublicRootID, _ objInfoStore.ID) 'Now get the Folders collection below the root Set myfoldercollect = myrootfolder.Folders Set recursefolder = myfoldercollect.GetFirst() 'Recurse it until we get the folder we are looking for Do While recursefolder.Name <> "Helpdesk" Set recursefolder = myfoldercollect.GetNext() Loop If Err.Number <> 0 Then response.Write "Could not find Helpdesk folder " & _ "under All Public Folders." response.End End If Set objFolder = recursefolder Set Session("HelpdeskFolder") = recursefolder Session("InfoStoreID") = objInfoStore.ID
Once the code finds the Helpdesk folder, it stores the CDO Folder object that corresponds to the Helpdesk folder as well as the unique identifier for the public folder InfoStore object because, from a performance standpoint, recursing the InfoStores and Folders collection in every ASP page in the Helpdesk application to find this information is expensive. Because we now have this information available across the entire session, the other pages will use the Session object for the folder whenever access to the Helpdesk folder is required.
Implementing Helpdesk Folder Security
When a user logs on to the Helpdesk application, the options displayed depend on the user's folder permissions. For example, if a user accesses the application and he has only Create Items permission for the Helpdesk folder, the ASP displays only the Submit A Help Ticket and Logoff options, as shown in Figure 11-8. However, if a technician who has Read Items and Create Items permissions for the Helpdesk folder logs on to the application, the ASP displays an additional option ”View Current Help Tickets, as shown in Figure 11-9.
This functionality is implemented in CDO by using the Fields collection on the Folder object. The Fields collection, a common collection across many of the objects in CDO, allows you to access specific properties stored for an object that CDO does not have an explicit object or property for. With the Fields collection, you can pass a unique identifier that corresponds to the properties you want to retrieve. In the next section of Helpdesk code, which is from the default.asp file, we pass in the unique identifier for the MAPI property ActMsgPR_ACCESS ( &H0FF4003 ), which contains a bitmask of flags corresponding to the user's permissions level for the current Folder object. The code then performs a logical AND on the returned value from the Fields collection by using the known bitmask of the MAPI_ACCESS_READ permission. If the result does not equal zero, the user can read items in the folder, and the ASP will display the View Current Help Tickets link on the helpdesk menu.
<h2>Please choose an option:</h2> <a href="default1.asp">Submit a help ticket</a><P> <% 'Check permissions on the folder to see whether the user has 'Read access. If the user does, the user must be a technician, 'so display the ability to view help tickets. nAccess = objFolder.Fields.Item(ActMsgPR_ACCESS) bCanPost = nAccess And MAPI_ACCESS_READ If bCanPost <> 0 Then %> <a href="render.asp">View current help tickets</a> <% End If %> <P> <a href="logoff.asp">Logoff</a>
You can pass many types of identifiers to the Fields collection. The Helpdesk application does not use the CdoPR_CONTAINER_CLASS ( &H3613001E ) property, but it demonstrates the type of information you can access through the Fields collection on CDO objects. It contains the message class of the default type of item contained in the folder. For example, if in Outlook you set the default item type for a Public Folder as contacts, the CdoPR_CONTAINER_CLASS property will contain IPF.Contact . How do you change the default item type in a folder programmatically through CDO? By using the Fields collection on a folder and this property, as shown in the next bit of sample code, the code changes the default item type of a folder to tasks. For example, if you decide you want to change the default content in the folder, you can avoid the trouble of creating a new folder, moving all the data, resetting permissions, and performing other administrative tasks by changing the default type of the folder. The code uses the InfoStores collection to retrieve the mailbox of the user. It then retrieves the root folder of the mailbox by using the RootFolder property. From that root folder, it retrieves the contacts folder in the mailbox. The next line of code uses the Fields collection on the folder to set a property that corresponds to the default item type for the folder. Finally, to make the default item-type change permanent, the Update method of the Folder object is called, committing the changes to the server.
Set oStore = oSession.InfoStores("Mailbox - Thomas Rizzo (Exchange)") Set oFolder = oStore.RootFolder.Folders("Contacts") MsgBox "Before Update: " & oFolder.Fields(&H3613001E) oFolder.Fields(&H3613001E) = "IPF.Tasks" oFolder.Update MsgBox "After Update: " & oFolder.Fields(&H3613001E)
Note | For the complete list of the identifiers you can use with the Fields collection in your applications, refer to cdo.chm on the companion Web site. On the Contents tab, look in the Appendixes section for the MAPI Property Tags document. |
Retrieving User Directory Information
If the user chooses Submit A Help Ticket from the Helpdesk menu, the file default1.asp is called. This ASP file displays a Help Request form, which is shown in Figure 11-10.
As you can see in Figure 11-10, some information about the user is already entered in the Help Request form. This information is dynamically pulled from the Exchange Server directory to help users save time when filling out requests. The enabling technology for this dynamic lookup primarily involves two CDO objects, the Recipient object and the AddressEntry object. A diagram illustrating the hierarchy of these two objects is shown in Figure 11-11.
The Recipient object represents a recipient of a message. You might wonder why a Recipient object is used to retrieve directory information from Exchange Server. There are two primary reasons. The first is that CDO does not explicitly support querying for directory information without first retrieving the AddressEntry object for the given user. The second is that adding the name of the user to a message as a Recipient and then using the Resolve method of the Recipient object is probably the easiest way to retrieve the AddressEntry object for a particular user. After resolving the name, you can call the AddressEntry property on the resolved Recipient object to retrieve the corresponding AddressEntry object. Just be sure to destroy the temporary objects you created for the Message and Recipient objects. The AddressEntries collection and AddressEntry object in CDO are shown in Figure 11-12.
The AddressEntry object contains the directory and address information for a user in the Exchange Server system. You can use the built-in properties of this object to access the e-mail address information for a particular user, but to access directory information (department, office location, and phone number), you must use the Fields collection of the AddressEntry object with MAPI property tags. The following code sample, which is taken from default1.asp, shows you how the Helpdesk application implements directory lookup for users:
Set objmessage = objOMSession.Outbox.Messages.Add 'Create the recipient Set objonerecip = objmessage.Recipients.Add 'Retrieve the e-mail address from the previous HTML form objonerecip.Name = Session("mailbox") 'Resolve the name against the Exchange Server directory objonerecip.Resolve 'You can add code to handle unresolved names here 'Get the address entry so that we can pull out template information Set myaddentry = objonerecip.AddressEntry Set myfields = myaddentry.Fields 'The numbers in parentheses are the hard-coded IDs for department, 'title, and so on Set mydept = myfields.Item(974651422).value Set mytitle = myfields.Item(974585886).value Set myworkphone = myfields.Item(973602846).value Set myoffice = myfields.Item(974716958).value If mydept = "" Then mydept = "None specified" End If If mytitle = "" Then mytitle = "None specified" End If If myworkphone = "" Then myworkphone = "None specified" End If If myoffice = "" Then myoffice = "None specified" End If Set objmessage = Nothing Set objonerecip = Nothing
This code first adds a new message to the Outbox of the user by calling the Add method on the Outbox folder. Then it adds a new Recipient object to the message. Because the ASP session contains the display name of the current user, this value is passed in as the Name property for the recipient. The code then calls the Resolve method on the Recipient object to make CDO check for ambiguous names in the directory. Once the user is resolved, the AddressEntry object for the user is retrieved using the AddressEntry property of the Recipient object.
From there, the Fields collection of the AddressEntry object is retrieved. The code then uses some MAPI property identifiers to retrieve specific information from the directory. If the information is unavailable in the directory, the code specifies that the value for the variables be None specified . To make sure the temporary Message and Recipient objects are released from memory, the code sets both objects to Nothing .
Posting Information in the Helpdesk
Once the user fills in the help ticket information, such as a problem description, he clicks the Post Now! button on the HTML form. The action of this HTML form sends the user information to another ASP file named posthelp.asp. The posthelp.asp file uses the CDO library to post this information to the Helpdesk public folder by creating a new Message object in the folder. The hierarchy for the Message object and its parent collection, Messages , is shown in Figure 11-13.
The Messages collection is accessed by calling the Messages property on a Folder object. The Messages collection consists of Message objects, which you can manipulate to change items in folders or to create new items. To add a new message to a folder, you use the Add method of the Messages collection ” essentially , it adds a new item to the collection. The type of message created depends on which folder you are calling the Add method in. For example, if you call the Add method in your Inbox, CDO will return a new Message object. If you call the Add method in your Calendar folder, CDO will return a new AppointmentItem object.
After adding a new item to the collection, you can set the properties for the item and then call the Send or Update method, depending on the type of item you created. If you used the Add method in your Outbox, you should call the Send method because e-mail messages are created in the Outbox. In the Helpdesk application, the script calls the Update method because the application does not e-mail new help tickets into the folder but rather posts them into the folder. The following script from posthelp.asp posts the information from the HTML form into the public folder:
Set objFolder = Session("HelpdeskFolder") Set recursefolder = objFolder 'Add a new message to the public folder Set oMessage = recursefolder.Messages.Add 'Set the message properties oMessage.Subject = Request("Subject") oMessage.Sent = True oMessage.Unread = True oMessage.TimeSent = Now oMessage.TimeReceived = Now oMessage.Type = "IPM.Task.Help Request" oMessage.Importance = Request("Priority") oMessage.Fields.Add "From User", 8, Request.Form("Contact_Email") oMessage.Fields.Add "Description", 8, Request("Description") oMessage.Fields.Add "Problem Type", 8, Request("Type") oMessage.Fields.Add "Product", 8, Request("Product") oMessage.Fields.Add "Phone", 8, Request("Phone") oMessage.Fields.Add "User Location", 8, Request("Location") oMessage.Fields.Add "Flag", 8, "Opened" 'Set the conversation properties oMessage.ConversationTopic = oMessage.Subject oMessage.ConversationIndex = objOMSession.CreateConversationIndex 'Post the message oMessage.Submitted = FALSE oMessage.Update Set oMessage = Nothing
As you can see in the code, some properties on the new Message object, such as Sent , Unread , TimeSent , TimeReceived , and Submitted , are explicitly set. These properties must be set because when you post an item into a public folder as the Helpdesk does, the underlying messaging system does not set these properties for you. They must be set before you call the Update method on the Message object for posted items; otherwise , you will receive an error.
The Sent property is a Boolean that determines whether the message has been sent through the system. Because we are posting information directly into the folder, we must set this property explicitly to True before calling the Update method. The Submitted property is also a Boolean that must be set to True before we call the Update method. The Submitted property determines whether the item has been submitted into the messaging subsystem.
The TimeReceived and TimeSent properties contain dates that tell the user when the message was received by the folder and when the user sent the message. You should set both of these properties to the current date and time before posting the item to the folder. The easiest way to do this is to assign them to the value returned by the Now function.
The Unread property is a Boolean that represents whether the current user has read the item. This property is not set automatically by CDO when you post an item into a public folder. For this reason, you must set this property to False before posting your item into the public folder.
Now that we have set the required properties to successfully post a message into a public folder, we can use some of the other properties on the Message object to implement the functionality of our application. Notice in the script that the Type property is set to IPM.Task.Help Request . The Type property corresponds to the message class of the item. When this property is set to a custom message class, Outlook and OWA users can use a custom form that handles that message class to open the item in the folder.
The Importance property in the script is set to the importance level that the user selected in the Priority drop-down list in the HTML form. This property has three possible values: CdoLow (0) , CdoNormal (1) , and CdoHigh (2) . Setting Importance to CdoLow (0) has the same effect as adding the down arrow icon to an e-mail message in Outlook; CdoHigh (2) has the same effect as the exclamation point icon in Outlook.
The Subject property is set to whatever the user typed in the Problem text box of the Helpdesk form. It is rendered as a hyperlink later in the application so that from their Web browser, technicians can click on a specific problem and drill down to the specifics about the help ticket and the user who submitted it.
You use the Fields collection of the Message object in the script to add custom fields to the item programmatically ”a powerful technique, because any items you create in CDO can use your fields. These custom properties, or fields, are then accessible from Outlook. Because the Exchange Server database is a semistructured database, you can even change or add new properties to the items in a folder without worrying about breaking a schema. This means that in your application, every item and its properties can have a different schema. Your application can dynamically add new properties to items depending on the input of the user.
The way you add new custom properties to an item is by using the Add method of the Fields collection. This is the syntax for the Add method:
Set objField = objFieldsColl.Add (Name, Class [, Value] [, PropsetID])
Note | Exchange Server is a semistructured database; Microsoft SQL Server is completely structured. With SQL Server, you must define your schema before you can start adding data, and the schema is usually fixed. With Exchange Server, every message can be different. For example, one message might have 0 attachments and another might have 15 attachments. Exchange Server is designed to efficiently handle this varying data. |
Passing the last two parameters to this method and catching the value returned by this method are optional. You can see in the preceding script that the code does not set an object variable to the return value of this function.
The first parameter that the Add method takes is the name of the custom property. This name is limited to 120 characters ; if you attempt to exceed this limit, CDO will return an error. The second parameter is the class, or data type, that you want to store in the property. The class parameter should pass one of the following values: vbArray (8192) , vbBlob (65) , vbBoolean (11) , vbCurrency (6) , vbDataObject (13) , vbDate (7) , vbDouble (5) , vbEmpty (0) , vbInteger (2) , vbLong (3) , vbNull (1) , vbSingle (4) , vbString (8) , or vbVariant (12) . The data type you will use most often in your applications is vbString (8) . As you can see in the Helpdesk script, all custom properties on the posted item use the string data type.
The third parameter, which is optional, is the value for the property. Usually you should pass this parameter to the method so you do not have to write extra code to initialize the custom property with the value.
The fourth and final parameter is PropsetID . This parameter is a GUID that uniquely identifies the MAPI property set to which the custom field should belong. In your applications, you will almost always use the default property set, which is assumed if you omit the PropsetID parameter. Only if you are developing a custom MAPI application that uses its own property sets will you ever need to set PropsetID .
In addition to adding new custom properties, the script sets the ConversationTopic and ConversationIndex properties for the help ticket before posting it into the folder. These properties are used by both CDO and Outlook to allow you to create threaded views of information in your folders. Your users might want to create these types of views in your applications, so you should set these properties in your code.
The ConversationTopic property is a string that describes the subject of the conversation. All the items in the same conversation have the same property value, and because ConversationTopic corresponds to the overall subject of the conversation, the most logical value for it is the Subject property of your message.
The ConversationIndex property is a hexadecimal string that represents the relationship between items in the same conversation. CDO and Outlook use this property to determine which items are replies to other items and how to thread these items in a view. Because you do not want multiple messages with the same index, CDO provides a method for generating unique conversation index values ”the CreateConversationIndex method on the CDO Session object. As you can see in the Helpdesk code, all you need to do is call CreateConversationIndex and assign its value to the ConversationIndex property for your item.
After setting all these properties, the script calls the Update method on the Message object, and a new help ticket is created in the folder. If you do not call the Update method, CDO will not commit your changes to the public folder.
Rendering the List of Helpdesk Tickets
When you create a Web application, one of the hardest parts to design is the user interface. You have to worry about using HTML tables to line up content, and you have to make sure these tables have the correct format and spacing to appear properly in a browser. The beauty of CDO is that you do not have to worry about the user interface. The CDO library has a companion library, the CDO Rendering library, which provides objects that automatically convert Exchange Server information to an HTML format in a preset layout. Figure 11-14 shows the relationships among the objects and collections of the CDO Rendering library.
The CDO Rendering library can not only render simple types of information such as your Inbox, but it can also leverage any custom table views you create in Outlook. For example, you can use the CDO Rendering library to render your Inbox as HTML, grouping messages by sender. The CDO Rendering library provides this functionality with a minimal amount of coding, as you will see. Plus, the formats that the library uses to render information to HTML are customizable, so if you want to change the color or the font of items to meet particular criteria or contain particular properties, you can easily do this by using the Rendering library objects. Figure 11-15 shows an HTML view of help tickets in a folder, created using the CDO Rendering library.
Similar to the Session object in the CDO library, the RenderingApplication object of the CDO Rendering library is the parent object of all other objects in the library. To create a RenderingApplication object, you use the CreateObject method and pass the ProgID of the CDO Rendering library, which is AMHTML.Application . In the Helpdesk application, the RenderingApplication object is created in the Global.asa file and given application scope in ASP so all sessions in the Helpdesk application can create individual objects from the global RenderingApplication object. This is a good practice to use in your CDO ASP applications.
The RenderingApplication object contains a number of properties, such as the code page or virtual root, that you can set and use throughout all the rendering objects created from the RenderingApplication object. When you first create your RenderingApplication object, most of the properties on the object will be filled in with default values. Most of these default values do not need to be changed unless you are developing completely customized applications that cannot use the defaults.
The most important property and the two most important methods of the RenderingApplication object to learn about are the ConfigParameter property, the CreateRenderer method, and the LoadConfiguration method. You'll use these in almost all of your ASP CDO applications. The LoadConfiguration method and the ConfigParameter property work together to tell the CDO Rendering library where to pull configuration information from ”such as the registry or the Exchange Server directory. This location information is used to retrieve specific configuration data from the selected data source. In the Global.asa file for the Helpdesk application, the name of the Exchange server that CDO communicates with is retrieved by using the LoadConfiguration method and the ConfigParameter property. The ConfigParameter property can retrieve other types of configuration parameters, such as whether anonymous access is enabled on the Exchange server, the organization and site names for the Exchange Server directory, and whether the http protocol is even enabled on the Exchange server.
The CreateRenderer method creates a new rendering object, which is attached to the current RenderingApplication object. This method takes an integer argument that specifies the type of rendering object to create. The value for this integer parameter can be CdoClassContainerRenderer (3) or CdoClassObjectRenderer (2) . In your applications, use the object renderer to render only specific properties on items, such as the subject or the text of the item. You should use the container renderer to render rows of information, such as all the messages in a folder.
In the render.asp file for the Helpdesk application, the following script first gets the global RenderingApplication object and then calls the CreateRenderer method to create a container renderer. The application uses a container renderer to display all the items in the Helpdesk folder rather than specific properties on a specific item.
'Create Rendering Application Set objRenderApp = Application("RenderApplication") 'Create Container Renderer id=3 Set objrenderer = objRenderApp.CreateRenderer(3)
The application then sets the DataSource property for the container renderer to the Messages collection in the Helpdesk folder. With any type of rendering object, you must set the DataSource property because it tells the rendering object what data to actually convert to HTML. The container renderer can accept an AddressEntries , Folders , Messages , or Recipients collection as its data source.
Set oMsgCol = objFolder.Messages 'Set the data source for the Rendering object to the 'helpdesk public folder objrenderer.DataSource = oMsgCol
The next step in the code figures out whether the application passed along a custom Outlook view name with the query string. If it did, the script uses that custom view name as the default view for the rendering object. This is an important point. You can use custom Outlook views as views in your rendering objects. This means you do not have to create views on the fly in your CDO applications and can instead create views in Outlook and then leverage them in your application. The rendering object supports views that contain sorting, grouping, and filtering ”support that will save you many hours of coding custom views.
If the application did not pass a view name with the query string, the default view named Helpdesk is used to render the tickets in the folder, as shown in the following code:
'Get the requested view from the query string requestedview = Request.QueryString("view") 'Get the Folder Views collection Set objviews = objrenderer.Views 'If there is no selected view, set it to Helpdesk view 'created in Outlook If requestedview = "" Then requestedview = "Helpdesk" End If 'Search the Views collection until you find the view i=1 Do While objviews.item(i).Name <> requestedview i=i+1 If err.number <> 0 Then response.write "The Outlook views were not created. " & _ "Please create the views according to the setup instructions." response.end End If Loop Set objview = objviews.Item(i)
The next step in setting up the container renderer is to enable a hyperlink on a field in the view so technicians can click on the hyperlink to retrieve the information contained in the ticket. If you do not include this step in your CDO applications, the rendering objects will return HTML without any clickable links. To create hyperlinks , you must first select the column in the view for which you want to create the hyperlink. In the Helpdesk application, the third column in the view is always used to create the hyperlink. You access the third column by using the Columns collection and the Column object of the TableView object that is represented by the custom Outlook view. The Column object has a property named RenderUsing that allows you to specify the HTML code to use when rendering that specific column. You specify not only the HTML but also substitution tokens within percent signs in the RenderUsing property, which CDO replaces with actual values when rendering the information to the Web browser. The two most commonly used substitution tokens are %obj% and %value% .
You use the %obj% token when you want to place the unique identifier for the item as a string in your HTML. This token is used in the Helpdesk application so the hyperlink on the third column passes the unique identifier for the ticket to the next ASP file, framemsg.asp. You use the %value% token when you want to place the actual value of the property into the HTML returned by the rendering object. The Helpdesk application uses this token to display the actual third-column property value in the view. For the Helpdesk view, this property value is the subject of the message. The following code shows you how to use both of these substitution tokens as well as the RenderUsing property and Column object:
Set objcolumns = objview.Columns Set objcolumn = objcolumns.Item(3) 'Change the column renderer so that it renders the subject field 'as an ahref with the entry ID of the message objcolumn.RenderUsing = _ "<a href='framemsg.asp?entryid=%obj%'>%value%</a>"
The final step in enabling the container renderer is to set the CurrentView property as the custom view just modified by the application. To do this, the application sets the CurrentView property equal to the TableView object we just modified, as shown in the following code:
'Set the current view equal to the view just selected objrenderer.CurrentView = objview
To actually render the information to HTML and return it to the browser, you must call the Render method on your ContainerRenderer object. The Render method takes four parameters. The first parameter is a Long data type that determines the style that the data should be rendered in. This parameter has two possible values ” CdoFolderContents (1) and CdoFolderHierarchy (2) :
-
CdoFolderContents is used to render the actual contents of the data source and not the child objects.
-
CdoFolderHierarchy is used to render child folders for a Folders collection. If you want to build an HTML page that displays a folder hierarchy for users to scroll through, you use the CdoFolderHierarchy style.
The second parameter, which is optional, is also a Long data type and specifies the page number at which rendering should begin. In the CDO Rendering library, you can have CDO automatically break up the content of a data source into data pages so that when the HTML is rendered by the library, the length of the resulting HTML table is not massive. By default, CDO breaks the content at every 25 rows in the HTML table. It does this to enhance the performance of your application as well as to make it easier for users to read the information. You can change the default number of rows rendered by setting the RowsPerPage property on the ContainerRenderer object.
The third parameter is for internal use only by the CDO Rendering library. You should always pass as its value. The final parameter is the Active Server Response object to which you want to send the HTML output from the Render method. This parameter should always be Response if you want to render the information to the browser.
When you have large amounts of information to render, you should be aware that CDO does not automatically generate a way to navigate rows on multiple pages, nor does it tell you whether you need to render multiple pages of information. Therefore, in your application, you must provide navigation elements if the number of rows in the table is larger than the value of the RowsPerPage property. You also must remember which page the user is currently rendering as well as the total number of pages. If the number of help tickets exceeds the RowsPerPage property, the render.asp file for the Helpdesk application will display graphical navigation arrows as well as text indicating the current page of information. When a user clicks on the Next Page or Previous Page arrow, the current page variable is either incremented or decremented, and this value is sent with the query string. The ASP script retrieves the value and renders the correct content on the page. If there are no previous or next pages, the graphical navigation elements are not displayed. The following code from render.asp handles viewing help tickets on multiple pages:
'Calculate total number of pages intMessageCount = oMsgCol.Count numrows = objRenderer.RowsPerPage intPages = (intMessageCount - 1) \ numrows intPages = intPages + 1 intCurPage = CInt(Request.QueryString("curpage")) If intCurPage > intPages or intCurPage < 1 Then 'Initialize it intCurPage = 1 ElseIf intMessageCount < 1 Then intCurPage = 1 End If <% If intCurPage <> 1 Then %> <a href= "render.asp?view=<%=requestedview%>&curpage=<%=intCurPage-1%>"> <img src = "left.gif" Align="Middle" border=0 Alt="Previous Page"></a> <% End If %>   Page <%=intCurPage%> of <%=intPages%>  <% If intCurPage <> intPages Then %> <a href="render.asp?view=<% response.Write requestedView %> &curpage=<%=intCurPage+1%>"> <img src = "right.gif" Align="Middle" border=0 Alt="Next Page"></a> <% End If %> <table border="0" width="65%"> <tr> <td><% objRenderer.Render 1,intCurPage,0,Response %></td> </tr> </table>
Notice how you can easily calculate the total number of pages you need to render to completely show all the information in your application. Take the number of messages minus 1, integer-divide that number by the number of rows per page set for the ContainerRenderer object, and then add 1 to that value. Therefore, if the number of messages is less than or equal to the number of rows per page, the integer division will return 0. This value will be incremented by 1, indicating that there is one page of information.
Rendering the Actual Help Ticket
When the technician clicks on one of the hyperlinks in the rendered list of help tickets, the application calls another ASP file, message.asp, to render the actual ticket for the technician, as shown in Figure 11-16. The technician can then view a number of different items for the ticket, resolve the ticket, or schedule a meeting with the customer who submitted the ticket.
The Helpdesk application uses DHTML to simplify navigating information contained in a ticket. It also uses ADO and queries an Access database to pull out the relevant information about the user's machine. This portion of the application shows you how you can combine CDO and ADO to make rich Web applications that access information from two types of data sources: Exchange Server and an ODBC/OLEDB database.
In the code for the help ticket page, the EntryID of the help ticket link that the user clicked on is retrieved from the Request.QueryString collection. The script then calls the GetMessage method of the CDO Session object to retrieve the message from the Exchange Server database. The GetMessage method is an easy and fast way to retrieve information from Exchange Server if you know the unique EntryID for the desired message. If you do not know the EntryID, you must search the folder where the message is stored or use a MessageFilter object to filter out only your message. The MessageFilter object is discussed in the Calendar of Events application later in this chapter.
The script then sets the Unread property of the message to False and calls the Update method of the Message object to save that property back to the database. Because CDO does not automatically update the Unread property for you, you must set it in code. After the code sets Unread , when the technician goes back to the table of tickets in the folder, the code displays all messages read by the technician as nonbold tickets. This functionality of not bolding read messages is provided automatically by the Rendering objects and ultimately makes your application easier to use. Also, the Exchange Server database maintains a per-user Unread property so each technician will receive different read and unread messages in the folder according to what each has actually read. The VBScript code for this functionality is shown here:
'Get the EntryID for the message from the query string Set objMessageID = Request.QueryString("entryid") 'Get the message by its ID Set oMessage = objOMSession.GetMessage(objMessageID, _ Session("InfoStoreID")) 'Set the message as read oMessage.Unread = FALSE 'Update the message in the folder oMessage.Update
The application then pulls out the status of the ticket, either Opened or Done , and also the name of the user who submitted the ticket. An ADO Connection object is created, and a connection to the Access database is established by the application. In ADO, you can specify values for the Open method on the Connection object to determine which OLE DB data source you want to open. In this case, the DSN name Helpdesk , which we created earlier on the machine, is passed as the parameter for the Open method. Once the connection is established, three queries are executed against three database tables to figure out the machine configuration for the current user. We accomplish this by using the name of the user retrieved from the help ticket. These queries use the Execute method of the ADO Connection object. This information is used later in the form. The following code shows the ADO connection and queries. (For more information about ADO and its object library, visit the Microsoft Web site at http://www.microsoft.com/data .)
'Start the ADO connection On Error Resume Next Set Conn = Server.CreateObject("ADODB.Connection") Conn.Open "Helpdesk" Set RS = Conn.Execute( _ "SELECT SystemChipType, SystemChipSpeed, SystemChipCount, " & _ "SystemOS, SystemRAM FROM tblMachine " & _ "WHERE Userid like '" & objuser & "';") Set RSIP = Conn.Execute( _ "SELECT CompName, IPAddress FROM tblNetwork " & _ "WHERE Userid like '" & objuser & "';") Set RSSoft = Conn.Execute( _ "SELECT SoftwareName, SoftwareVersion FROM tblSoftware " & _ "WHERE Userid like '" & objuser & "';")
The script retrieves the user information from the Exchange Server directory and stores it in variables. (You saw this code earlier when we discussed submitting a help ticket.) Then the body of the actual ticket is displayed using DHTML. The DHTML code includes some JavaScript to allow the user to dynamically expand or collapse the different sections of the help ticket, such as system information or the description of the problem.
Creating the Calendar Information
One interesting section in the body of the HTML is the drop-down section of calendar information, shown in Figure 11-17. This section allows technicians to view the free/busy information for the user and for themselves at the bottom of the help ticket page. With this information, the technician can quickly find time slots that are open for both the technician and the user.
Note | To obtain up-to-the-minute calendar information for a user on the Help Request page, you might have to adjust the calendar settings in Outlook. By default, Outlook updates the calendar free/busy information on the server every 15 minutes. You can decrease this time in Outlook by choosing Options from the Tools menu, clicking Calendar Options, and then clicking Free/Busy Options. If you do decrease this, you should remember that this will cause more network traffic in your system. |
This calendar information is created by using the calendaring functionality of the CDO library. The code for the drop-down section is shown here:
<% bcGrayM = "#c0c0c0" 'gray bcGrayD = "#909090" buildmonth = Request.QueryString("m") buildday = Request.QueryString("d") buildyear = Request.QueryString("y") builddate = buildmonth & "/" & buildday & "/" & buildyear dtCurrentDay = DateValue(builddate) arrBGColors= _ Array(bcGrayM, "#99ccff", "#0000ff", "#940080", "#ff0000") Dim MeetingPlanner(2) %> <B><FONT SIZE=3><SPAN CLASS=ex TITLE="Calendar Information" </SPAN></FONT></B> <IMG WIDTH=15 HEIGHT=13 SRC="addarrow.gif" ID="imgArrow4"><P> <TABLE ID = "CalInfoChild" BORDER = 0> <TR> <td nowrap=nowrap width="355" align="right"> <A href="message.asp?entryid=<%response.write objMessageID%> &m=<%=Month(dtCurrentDay-1)%>&d=<%=Day(dtCurrentDay-1)%> &y=<%=Year(dtCurrentDay-1)%>" target="main"> <img src="left.gif" border=0 alt="Previous Day"></a></td> <td nowrap=nowrap width="10"></TD> <td nowrap=nowrap border=0><i><b> <%=MonthName(Month(dtCurrentDay)) & " " & Day(dtCurrentDay) & _ ", " & Year(dtCurrentDay)%></b></i></TD> <td nowrap=nowrap border=0 width="10"></td> <td nowrap=nowrap width="20" align="right"> <A href="message.asp?entryid=<%response.write objMessageID%> &m=<%=Month(dtCurrentDay+1)%>&d=<%=Day(dtCurrentDay+1)%> &y=<%=Year(dtCurrentDay+1)%>" target="main"> <img src="right.gif" border=0 alt="Next Day"></a></td> </Table> <table ID = "CalInfoChild2" cols=50 bordercolor=#FFFFFF border=1 cellspacing=0 cellpadding=0> <tr> <td nowrap=nowrap width="102"> <td colspan=2> </td> <% For idx = 0 to 23 %> <td bordercolor=#000000 colspan=2 align=left width="24"> <font size=-2%> <%= CStr((((idx + 11) Mod 12) + 1))%> </font></td> <% Next %> </tr> </TABLE> <% 'Loop from 3 to 4 For x = 3 to 4 If x = 3 Then Set objRecip = objOMSession.CurrentUser Else set objRecip = objonerecip End If 'Initialize the string aFB = "" aFB = objRecip.GetFreeBusy( dtCurrentDay, dtCurrentDay+1, _ 30, true) szFreeBusy = aFB %> <table ID = "CalInfoChild<%=x%>" Border=0> <tr bordercolor=#000000> <td nowrap=nowrap bgcolor="#ffffff" width="100"><font size=-1> <%= objRecip.Name %><br></font></td> <% If Len(szFreeBusy) = 0 Then %> <TD><font size=2>Free/Busy Information is not available </font></TD> <% Else For idy = 1 to 48 sCell= Mid( szFreeBusy, idy, 1) %> <td bgcolor=<%= arrBGColors(CInt(sCell)) %> width="9"> </td> <% Next 'idy End If Next 'x %> </table> <TABLE ID="CalInfoChild5" border=0> <TR> <td nowrap=nowrap width="100"><b>Legend:</b></TD> <TD bgcolor=<%= arrBGColors(1) %>> </TD> <TD> Tentative </TD> <TD bgcolor=<%= arrBGColors(2) %>> </TD> <TD> Busy </TD> <TD bgcolor=<%= arrBGColors(3) %>> </TD> <TD> Out of Office </TD> </TABLE>
First the code tries to retrieve, from the query string, the day, month, and year values passed by the application. When the user first clicks on a hyperlink from the list of help tickets, these date values are filled with the current day, month, and year from the Web server machine. The code then builds a date from the values, such as 10/12/2003, so the string containing the date value can be used as a parameter to the DateValue function in VBScript. This function returns a Variant of the subtype Date so you can use it in the rest of the code.
The application then builds an array of information. This array, named arrBGColors , comprises background colors used to render the free/busy information to the Web browser. These colors correspond to the Outlook colors for rendering calendar information, such as blue for busy slots, light blue for tentative slots, and purple for out-of-office slots. You'll understand why we place these colors into an array when we look at the GetFreeBusy method in CDO and see how the values are returned from this method and parsed by the application.
The next step in the code is to render the navigation elements to move to the previous or next day in the calendar and also to display the date the technician is viewing. The navigation elements to move to the next and previous days are implemented using hyperlinks, which pass the next or previous date, broken up into day, month, and year, across the query string to message.asp. To retrieve the day, month, and year from the dtCurrentDay variable, which has the form 12/31/2003, the VBScript functions Day , Month , and Year are used.
The code then uses a For...Next loop from 0 through 23 to draw a table that creates the time values across the top of the free/busy information. Starting at 12 A.M., the code draws table elements until 11:30 P.M. the same day. Adding 11 to the current index of the loop, using the Mod operator to return the remainder when divided by 12, and then adding 1 to that value produces the correct identifiers for the time slots. The code uses the CStr function to convert the number returned by the formula to a string value.
The next portion of the code uses the calendaring features of CDO. Figure 11-18 shows some of the calendar-related objects in the CDO library. The CDO library provides extensive support for building calendaring applications: the Helpdesk application shows how to use the GetFreeBusy method and the meeting request functionality of CDO, and the Calendar of Events application, which we'll examine later in this chapter, shows appointment filtering and rendering of calendars using the Rendering library.
A For...Next loop is used to loop through the code twice. The loop starts at the number 3 and continues through the number 4. The loop does not start at the number 1 because the index of the loop is used to create unique <DIV> elements in the HTML code so the JavaScript checkExpand function can find the tables using them. Because this code is fairly generic, you can use it in other applications that render tens or hundreds of blocks of free/busy information for users rather than blocks for only two users. All you do is change the ending point of the loop and pass in the targeted users' free/busy information as an array of names.
The code uses the GetFreeBusy method of the CDO Recipient object to return the free/busy calendaring information for the current user, which is retrieved using the CurrentUser property on the CDO Session object, and the name of the person who submitted the help ticket, which is stored in the Recipient object named objonerecip . The GetFreeBusy method in CDO is exactly the same as the GetFreeBusy method in the Outlook object library. You must pass the following four parameters to this method:
-
The start time of the first slot you want to retrieve.
-
The end time of the last slot you want to retrieve.
-
The interval, in minutes, of each of the slots.
-
A Boolean specifying whether you want to have CDO return full formatting for the slots. Full formatting will inform you of the exact nature of busy slots: CdoFree (0) , CdoTentative (1) , CdoBusy (2) , or CdoOutOfOffice (3) . If you do not enable full formatting, CDO will return for free and 1 for busy.
For GetFreeBusy , CDO returns the most committed value in the time slots. This means that if a user has overlapping appointments in the slot and one is tentative and the other is out-of-office, CDO returns the out-of-office value. CDO does not return "free" for time slots unless the entire time slot is free. Be careful when you work with free/busy information far beyond the current date. By default, free/busy information is published only two months past the current date. If you query beyond the published information for free/busy, CDO will return free slots even though the user might have appointments during those slots. You can ask your Outlook users to change the number of months to a maximum of 12 months past the current date for free/busy information if you need longer term information. They can change this number of months in Outlook by choosing Options from the Tools menu, clicking Calendar Options, and clicking Free/Busy Options.
The return value for this method returns a string of numbers that correspond to the free/busy status of the user for all the time slots you specified. Your code must then parse the string and make a graphical representation of this information in your application.
The Helpdesk application parses this string by first looking at the length to make sure it's not zero, which would mean there is no free/busy information for the user. It then uses a For...Next loop and the VBScript Mid function to retrieve each character from the string and display the free/busy status for the user in an HTML table. Notice in the code that the value from the retrieved free/busy status interval is used as an index for the arrBGColors array of colors we created earlier: CdoFree (0) displays light gray, CdoTentative (1) displays light blue, CdoBusy (2) displays blue, and CdoOutOfOffice (3) displays purple. The For...Next loop ranges from 1 through 48 because we specified 30 minutes as the interval for the time slots in the call to the GetFreeBusy method. If you calculate the number of 30-minute intervals from 12 A.M. to 11:30 P.M., the result turns out to be 48.
Creating a Meeting with the User
Once a technician finds time slots in the Calendar Information section of a help ticket that are open for both the technician and the user, she can schedule a meeting with the user to troubleshoot on site. She can schedule a meeting by first typing the date and time for the appointment in the text boxes at the top of the help ticket, as shown in Figure 11-19.
When a technician types a date and a time and then clicks Create Appointment, createcal.asp is called by the application to create the actual meeting request, as shown in this code:
<% 'Convert the passed-in date to a vbDate Set querydate = Request.Form("Date") apptdate = CDate(querydate) Set querytime = Request.Form("Time") appttime = TimeValue(querytime) compdatime = apptdate & " " & appttime compdatime = CDate(compdatime) 'Add a new message to the user's calendar Set calfolder = objOMSession.GetDefaultFolder(0) Set CalRequest = calfolder.messages.add 'Set the message properties CalRequest.Subject = oMessage.Subject CalRequest.StartTime = compdatime CalRequest.EndTime = DateAdd("n", 90, CalRequest.StartTime) CalRequest.Location = myoffice CalRequest.ResponseRequested = TRUE set meetingrecip = CalRequest.Recipients.Add meetingrecip.Name = objUser meetingrecip.Resolve CalRequest.ReminderSet = True CalRequest.ReminderMinutesBeforeStart = 30 CalRequest.MeetingStatus = 1 CalRequest.Send If Err.Number = 0 Then response.write "<SCRIPT LANGUAGE = 'JavaScript'>" response.write "window.alert('Meeting successfully created!');" response.write "</SCRIPT>" Else response.write "<SCRIPT LANGUAGE = 'JavaScript'>" response.write "window.alert('Error: " & Err.Number & " " & Err.Description &"');" response.write "</SCRIPT>" End If %>
First the code converts the passed-in date to a Date data type and the passed-in time to a Date . Then it combines the two and converts the results to a Date so the date can be passed to the CDO property StartTime to indicate the start time of the appointment. The code uses the GetDefaultFolder method of the CDO Session object to retrieve the calendar folder of the technician. The GetDefaultFolder method in CDO is similar to the GetDefaultFolder method in the Outlook object library, but be careful when you use them because the values for the constants that represent the folders are different in the two libraries.
By using the Add method on the Messages collection in the calendar folder, CDO automatically adds a new AppointmentItem object to the collection. The code then sets the properties for this new AppointmentItem object to turn it into a MeetingItem object, which is sent to the user.
To create a meeting request in CDO, you must set some specific properties on the AppointmentItem object to turn it into a MeetingItem object. First you need to add some recipients to the inherited Message object by creating a Recipient object in the Recipients collection. In the Helpdesk application, the name of the person who submitted the help ticket is added and resolved against the address book as a recipient for the MeetingItem object.
You then set the MeetingStatus property to CdoMeeting (1) for the AppointmentItem object you created so the current user ”the technician ”is set as the meeting organizer in the Organizer property. The MeetingStatus property can take other values as well:
-
CdoNonMeeting (0) , the default, which tells CDO that the appointment being organized is a regular appointment and does not represent a meeting.
-
CdoMeetingCanceled (5) , which indicates that the meeting organizer has canceled the meeting.
Although the ability to cancel a meeting is not included in the Helpdesk application, here is some information about it. To cancel a meeting, you call the Send method again on the object to send the cancellation to all attendees. Also, be sure to set the object that holds the meeting to Nothing . If you are the organizer of the canceled meeting and Outlook is the main calendar store for your users, you must also call the Delete method on the Message object that is the parent of the MeetingItem object you just canceled. If you do not do this, you might get unexpected results when you work with the folder and its contents in the future.
You should also set the ResponseRequested property. This property takes a Boolean that tells CDO whether the meeting organizer wants responses to the meeting request. You should set this property to False only if you want to send out an FYI meeting request, which adds the item to a user's calendar but does not need to track whether the user has accepted or rejected the item. For example, you can set the property to False if you are sending out meeting requests for all the holidays in a year but do not actually care which holidays your users decide to accept on their calendars.
The preceding script sets some general properties for the appointment item, such as the Subject , which is the problem contained in the ticket. It also sets the StartTime and EndTime properties of the appointment so the EndTime is 90 minutes after the StartTime . The Location property is set to the office location for the user, which is pulled from the Exchange Server directory. The ReminderSet and ReminderMinutesBeforeStart properties are set to True and 30 minutes before the appointment, respectively, to make sure both parties remember that they need to meet.
The final step when you create any meeting request is to call the Send method on the MeetingItem object, which sends the request to the recipients you invited to the meeting. If any of the properties you set are incorrect or empty, CDO will return an error after calling Send . For this reason, the code checks the Err object in VBScript. Depending on whether an error occurred, the JavaScript client code will display a success message or an error message. Figure 11-20 shows the success message. If a property is empty, such as the Office property for an Exchange user, the error message "Error 448: Named argument not found" is displayed.
After the meeting request is sent, the user can use any client to accept or decline the meeting request. You can even send meeting requests to users over the Internet; as long as they are using Outlook or OWA, they can view and accept or decline meeting requests. You cannot, however, view the free/busy information of users on different systems. Figure 11-21 shows the scheduled meeting in the user's Outlook calendar.
Resolving the Help Ticket
The technician can mark a help ticket as resolved. Resolving a help ticket consists of setting the status of the ticket to Done and sending an e-mail message to the user who submitted the ticket explaining that the issue has been resolved. The code that resolves the help ticket is in the resolved.asp file. This code shows you how to update fields on a message and also how to send e-mail messages using the CDO library:
<% If InStr(Request.Form("Action"), "Resol") then Set objFolder = Session("HelpdeskFolder") Set recursefolder = objFolder Set objMessageID = Request.QueryString("entryid") Set oMessage = objOMSession.GetMessage(objMessageID, _ Session("InfoStoreID")) 'Retrieve the message flag and set it Set msgstatus = oMessage.Fields("Flag") msgstatusid = msgstatus.ID oMessage.Fields(msgstatusid) = "Done" oMessage.Update 'Send a resolved message to the user stating that the 'problem was resolved Set objNewMsg = objOMSession.Outbox.Messages.Add objNewMsg.Text = "Your problem: " & oMessage.Subject & _ " was resolved on " & Now & chr(10) & chr(10) & _ "Please see the helpdesk FAQ at " & _ "http://exserver/faq/ for commonly asked questions." objNewMsg.Subject = "Resolved: " & oMessage.Subject Set objonerecip = objNewMsg.recipients.add objonerecip.Name = oMessage.Fields("From User") objonerecip.Resolve 'Send the message without showing a dialog box objNewMsg.Send showDialog=False End If %>
The script retrieves the help ticket from the folder and then uses the Add method on the Messages collection to add a new message to the Outbox of the technician. The script sets the message text by using the Text property of the Message object. The Text property can contain only plain text. It does not support formatted text.
The code then sets the Subject of the message and adds recipients to the Recipients collection. The recipient for this message is the user who submitted the ticket. The code uses the Resolve method of the Message object to make sure there are no ambiguous recipients on the message. Finally, it calls the Send method to send the message to the user. Figure 11-22 shows a sample of the e-mail received by the user.