Integrating Enterprise Data with Microsoft Outlook

Introduction

This white paper provides an overview of several of the current technologies that Microsoft® Outlook application developers should consider when attempting to integrate Outlook workflow applications with Enterprise data. While Outlook does not currently offer its own native data access functionality, the Item.Application.CreateObject method opens the object door to either Open Database Connectivity Direct (ODBCDirect), which is a subset of Remote Data Objects (RDO) 2.0 or ActiveX Data Objects (ADO). Both of these object models provide fast and efficient access to back-end enterprise data. You can also create ActiveX components to communicate with Enterprise data. An ActiveX component provides a middle-tier in your Workflow application. Using Distributed Component Object Model (DCOM) and Microsoft Transaction Server, these components provide greater security, performance, and error trapping over the complexities of Outlook data access calling either ODBCDirect or ADO from Visual Basic Script (VBScript). I’ll show you a simple ListServer object developed in Microsoft® Visual Basic® 5.0 that provides significant performance enhancements for accessing static data that will be presented to the user in an ActiveX ListBox or ComboBox control on an Outlook form.

Enterprise data encompasses many forms of data storage-from departmental data that resides on a local area network (LAN)-based database such as Microsoft® Access or Paradox®, corporate data in a relational database management system (RDBMS) such as Microsoft® SQL Server™, Informix® OnLine Dynamic Server, and ORACLE® 7.x Sever, or legacy mainframe data stores such as VSAM, IMS, and DB2®. I’ll show you several different approaches to integrating Enterprise data with Outlook applications. No one method is preferable over the others; the final choice depends upon the nature and design of your workflow application. After this presentation, you should be able to make an informed choice about data access methods depending upon your design goals and data stores.

Why Integrate Enterprise Data With Outlook Applications?

As companies develop workflow applications using the rich messaging and forms development environment of Microsoft® Exchange 5.0, some of these applications will require integration with other corporate data stores. Enterprise data stores reside in a variety of DBMS-from LAN file-based data to mainframe data.

Outlook/Microsoft Exchange applications store their data in messaging application programming interface (MAPI) data stores. While MAPI data stores are adequate for many workflow applications, more sophisticated applications require additional links with existing corporate data that inhabits more traditional database containers. Additionally MAPI data stores do not lend themselves currently to relational database design. One-to-many relationships cannot be modeled in the typical MAPI folder. You will find that you can add relational design principles to your workflow application by accessing external data and linking that data to your MAPI data store in a relational manner. I’ll discuss this design concept when I take you step by step through the Northwind Structured Query Language (SQL) Order Application.

For example, you could design a billing and time tracking application using the Outlook contacts and tasks folders. What happens when the design specification calls for adding general ledger (G/L) codes to specific tasks so that the billing for these tasks can be sent to a corporate invoicing application? If you are the application designer, you know that it’s not practical to code the G/L codes and descriptions into the Column property of an MSForms ComboBox. You’ll have to retrieve the G/L account data from a database in your organization and then load the data into your combo box. Once you have crossed the bridge named enterprise data, you’ll need to consider issues relating to security, performance, data integrity, and scalability. The design alternatives that confront you initially might seem somewhat daunting. The choice that you will finally make depends upon the scope of your project and the depth of your programming resources. I’ll try to point out the pluses and minuses of each approach.

Methods of Accessing Data in Outlook Forms

Currently there are two principle methods of accessing data in Outlook forms. The first and primary method involves writing VBScript code and using the Item.Application.CreateObject(“ProgID”) to instantiate an object model that provides data services to your application. RDO 2.0 ships with Visual Basic 5.0 Enterprise Edition. ODBCDirect, which is part of the DAO 3.5 object model, is in fact a subset of RDO 2.0. Its properties and methods closely resemble those of RDO. RDO implements a thin wrapper over ODBC and provides a high level of developer control and substantial performance gains over Jet and DAO 3.0. However, the RDO object model is complex and hierarchical, a feature which makes it difficult to write and debug in the somewhat constrained environment of VBScript.

ActiveX Data Objects 1.0 (ADO) is Microsoft’s data access object model of the future. You should heed the following statement from the recently published Data Access Interfaces: “While ADO represents an evolution of both DAO and RDO into a single, simplified, and extensible interface, Microsoft plans that later versions of ADO will supersede all DB-Library, DAO, and RDO functionality.”

While my testing of the different data access interfaces showed ADO to be slower than RDO when called from Outlook’s VBScript, this situation is bound to change in the future as OLE DB drivers are created by major database vendors. Currently ADO is the preferred data access object model for Internet Information Server 3.0. One of the major benefits of ADO is that it requires fewer calls to achieve the same result as RDO. In Outlook’s VBScript environment all variables must be dimensioned as variants. Consequently, all objects pertaining to a given object model are late bound. Late binding of objects strongly suggests that a simpler object model and fewer calls to properties and methods will result in better performance. ADO offers the benefits of simplicity without sacrificing functionality.

ActiveX components let you encapsulate RDO or ADO functionality within your own objects that can be instanced from VBScript’s CreateObject method. I’ve included both code and components for the ListServerRDO and ListServerADO ActiveX components that implement a CListServer class. The CListServer class was created using Visual Basic 5.0 Enterprise edition. The purpose of this class is to supply a variant array to either Outlook’s MSForms ListBox or ComboBox controls. Both of these controls have List and Column properties. If you assign a variant array to the Column property, the control is populated with data from the array in one statement. The Column property is currently the only means of creating a multicolumn MSForms list box or combo box. In the combo box, the BoundColumn property allows you to determine which column of the drop-down list is bound to the edit portion of the control. Additionally you would bind the edit portion of the combo box control to a user-defined field on an Outlook form. Database filled combo and list boxes are important elements of Outlook forms that use Enterprise data.

The benefits of custom components for Outlook forms are numerous. First, you can tailor the data access functionality in your component to a specific business application. Security can be tightened within the component framework. If you desire, you can remove the ConnectString property and place all login passwords in the component itself. Error trapping within Visual Basic 5.0 is superior to error trapping within VBScript. You should use the Err.Raise method in the component to return errors to your Outlook form. Performance is enhanced because Visual Basic components use early binding of all data interface objects rather than the late binding of VBScript. For application developers who want to package their components for enterprise distribution, you can use Microsoft Transaction Server and DCOM to distribute those components securely over your network. By using technologies such as Microsoft Transaction Server, systems network architecture (SNA) Server, and the forthcoming “Cedar” (a Microsoft Transaction Server component that allows intranet, Internet and client/server applications to interoperate with mainframe programs), Outlook developers will be able to reach data on the mainframe and create components that provide that data to Outlook forms. For additional information regarding Microsoft’s mainframe interoperability strategy, see “Integrate the Enterprise” (MSDN Library, Backgrounders, Enterprise Computing Articles).

ActiveX controls will also become available for Outlook forms development. At this point the native MSForms controls on Outlook forms can only be bound to MAPI user-defined fields. Any data binding to remote databases must be accomplished through code. The ActiveX Outlook data control which was in alpha during this writing will provide a means of accessing ODBC compliant databases without writing VBScript code. Finally, the Visual Basic 5.0 Custom Control Edition offers the avenue of customizing ActiveX controls for Outlook forms. You can create those forms on your own or purchase them from third-party vendors.

Here is a summary outline of the technologies discussed above for providing data access to Outlook forms:

Using Item.Application.CreateObject (“ProgID”)

ODBCDirect (RDO 2.0)

General Features:

Outlook-specific features:

ActiveX Data objects (ADO)

General Features:

Outlook-specific features:

ActiveX components

General features:

Outlook-specific features:

Using ActiveX Controls

Outlook data control

ActiveX custom controls

Creating an ActiveX Component for Outlook Data Access

The initial point of creating the CListServer class in its various flavors was to provide a testing environment for data access methods through the CreateObject method. Since VBScript does not implement Win32 API calls, CListServer also provides a GetTick method which simply is a wrapper around the GetTickCount API function. GetTickCount returns the number of milliseconds elapsed since Windows started running. GetTick provides an accurate method of determining elapsed time. However, the real benefit of Automation components in Outlook forms is that they can provide a middle tier between the Outlook form and your data store. You can create a persistent database connection in your ActiveX component that serves many users opening forms simultaneously. It is not necessary to open a connection for each user that opens a form. Moreover, you can create static variant arrays for recordsets that are required consistently on the forms in your application. For example, the cmbSuppliers combo box in the Northwind SQL application shows all suppliers so that the end user can select a supplier for the items they wish to order. The GetSuppliers method of CListServer can return the variant array to Outlook with a performance gain in the order of 100-300 times faster than using ODBCDirect natively in VBScript.

The GetSuppliers and GetShippers methods in CListServer provide variant arrays based upon a predetermined select statement for the Nwind database upsized to SQL Server. To provide greater functionality, the ConnectString property lets you set a connect string which establishes a persistent connection to a remote database through either RDO or ADO. Finally, you call the GetList method which takes a SQL string as its argument and returns a variant array containing the recordset created by the SQL statement.

For purposes of simplicity, the Northwind SQL Order sample application does not use the CListServer class to access data in SQL Server. The ListServer folder provided with this article contains several examples with VBScript code for using an ActiveX component for data access. I’ll leave it to you to adopt the ListServerADO and ListServerRDO components in the Northwind SQL application. Remember that you can embrace and extend this technology so that all remote data inserts, updates, and deletes are completed via component technology. You’ve moved the bulk of your coding from the sparse environment of VBScript to the robust features of Visual Basic 5.0. C++ developers can also create Outlook ActiveX components using Microsoft Visual C++ 5.0 Enterprise edition.

Turning the Tables: Accessing Outlook Data for Enterprise databases

If you need to turn the tables and access Outlook data in order to write that data to a remote data store, what options do you have? Although this article focuses on bringing enterprise data into Outlook workflow applications, I’ll briefly discuss the current options you face when you want to bring MAPI data into a database. An Microsoft Exchange indexed sequential access method (ISAM) driver is available which can link or import an Microsoft Exchange folder (Mailbox or Public folder) or an Microsoft Exchange Address Book (including the GAL) with Microsoft Access.

Download the Outlook Microsoft ExchangeWizard at http://www.microsoft.com/AccessDev/AccWhite/ExchWiz.htm. This URL also contains a comprehensive article on “Accessing Exchange and Outlook Data Using Visual Basic.” You should be aware however that the Microsoft Exchange ISAM contains several constraints including the following:

If you need greater functionality, you should consider (surprise, surprise) building an ActiveX component that uses a combination of the Microsoft Outlook Object Library, the ActiveX Messaging Library 1.1 (available with Microsoft Exchange Server 5.0), and either DAO 3.5, RDO 2.0, or ADO 1.0. An ActiveX component will give you full reciprocal access between your MAPI data store and your Enterprise data.

Outlook ListServer ADO and Outlook ListServer RDO

Two components are included with this article. ListServerRDO uses RDO to provide variant arrays to MSForms combo and list box controls on Outlook forms. ListServerADO uses ADO to accomplish the same functionality. Here are the step-by-step installation instructions for ListRDO. To install ListADO, just follow the same steps but substitute ListADO for ListRDO.

Installing ListRDO

  1. Copy ListRDO to an empty subdirectory.

  2. Double-click ListRDO in the Explorer. ListRDO is a self-extracting Zip file.

  3. Double-click Setup in the subdirectory where you copied ListRDO.

  4. Follow the installation instructions in the Setup program. ListServerRDO and ListServerRDODLL will be registered for you automatically. ListServerRDODLL provides exactly the same functionality as ListServerRDO except that it uses an ActiveX DLL instead of an ActiveX EXE.

  5. VB5 source code is provided in the SourceRDO.zip file which is contained in ListRDO.

Using ListServerRDO

  1. Dim a script-level object in VBScript named objListRDO.

  2. Set objListRDO = Item.Application.CreateObject(“ListServerRDO.CListServer”)

  3. Now you can use the properties and methods of CListServer. See the ListServerRDO Example form in the ListServer folder of Enterprise Data.pst for sample VBScript code. Basically you set a ConnectString property with DSN, UserID, and Password to establish a connection to the remote database. Once the ConnectString has been passed to the ActiveX component, you can call the GetList method to obtain a variant array which you can then assign to the Column property of an MSForms control.

Running the Visual Basic 5.0 Source Code

  1. You must have Visual Basic 5.0 Enterprise edition installed.

  2. Unzip SourceRDO.zip to a directory where you will store this project.

  3. Double-click ListServerRDO.vbp in the directory where you unzipped SourceRDO.zip.

  4. When the project loads, you will see an Alert Box warning you that VB was “Unable to set the Version Compatible Component.” Press OK to dismiss this warning. Setting the version compatible component ensures that each time you compile your component you do not create a separate ClassID in the registry. The version compatible component is now set to No Compatibility in the Version Compatibility frame.

  5. Select the Project | ListServerRDO Properties command, click the Component tab, and select the Compatibility tab. If you want to recompile the component, you should set Version Compatibility option to Binary Compatibility. See “When Should I Use Version Compatibility?” in Visual Basic 5.0 Books Online.

Testing Performance With Sample ListServer Forms

You can test the performance of ActiveX components with the Outlook forms contained in the ListServer folder of Enterprise Data.pst. When you press a command button on the sample ListServerRDO and ListServerADO forms, the GetTick method is called to begin timing the process. When the process of retrieving a variant array and populating the ComboBox1 control is complete, the GetTick method is called again and the result in milliseconds is displayed in a message box. An ActiveX component, once instantiated, offers the best performance gains when retrieving data from a remote data source. ADO in its current version 1.0 iteration is slower than RDO. As stated above, this situation is likely to change in the future as ADO begins to come in ‘flavors’ optimized for different environments. Here’s a code fragment that demonstrates how the ListServerRDO object is instantiated and called from VBScript:

'Script Level Declarations
Dim ComboBox1
Dim objListRDO
Const rdDriverNoPrompt = 1
Const rdOpenStatic = 3
Sub Item_Open()
Set ComboBox1 = Item.GetInspector.ModifiedFormPages _
("ListServer Example").Controls("ComboBox1")
Set objListRDO = CreateObject("ListServerRDO.CListServer")
End Sub
Sub cmdRDOGetList_Click()
Dim lngStart
Dim lngEnd
Dim strSQL
ComboBox1.Clear
strSQL = "Select CompanyName, Phone, SupplierId "
strSQL = strSQL & "from Suppliers ORDER BY CompanyName"
lngStart = objListRDO.GetTick()
'Modify this connect string if necessary
objListRDO.ConnectString = "DSN=NwindSQL;UID=sa;PWD="
ComboBox1.Column = objListRDO.GetList(strSQL)
lngEnd = objListRDO.GetTick()
MsgBox "Elapsed Time: " & (lngEnd-lngStart) & "milliseconds", vbInformation
End Sub

The chart shown in Figure 1 provides a dramatic illustration of the performance benefits of ActiveX components.

Figure 1. Performance Data for ActiveX Components

Overall Recommendations

While it is not possible to draw conclusions that apply to every Outlook application development project that integrates with Enterprise data, the following overall recommendations should be looked at closely when you devise a method for reading and writing remote data in your Outlook forms:

  1. Follow generally accepted client/server design principles. Don’t bring more information from a remote database into your Outlook form than is absolutely necessary. See “Building Successful Client/Server Applications” (MSDN Library, Backgrounders, Enterprise Computing Articles) for a detailed list of accepted principles. Consider calling stored procedures from ActiveX components rather than from within VBScript. Let VBScript call a method in an ActiveX component which in turn calls the stored procedure and returns errors to the calling application. Also maintain referential integrity by using two-phase commits when you write to the remote database. Use the CommitTrans, BeginTrans, and Rollback methods to ensure data integrity.

  2. Remember that an Outlook form is not a data browsing application.

  3. Scalability can dictate which data access solution you employ. An Outlook application that serves 10 users in a small workgroup will mandate a different approach than an application that serves hundreds of simultaneous users.

  4. Consider encapsulating business rules into ActiveX components.

  5. Remember that all remote object calls in VBScript are late bound and consequently suffer a performance penalty. Wrap your calls into one call and then perform remote object calls in an ActiveX component which can accomplish early binding.

  6. If your application requires additional security and maintainability, consider using Microsoft Transaction Server to package your components for distribution through DCOM.

Northwind SQL Order Sample Application

Overview

The Northwind SQL Order application illustrates the use of ODBCDirect with Outlook forms. An ODBCDirect connection is established with a SQL Server database created by upsizing the Northwind sample database that ships with Access 97. If you did not install the sample databases with Access 97, you must use Setup to install the Northwind sample database. Once you have installed northwind.mdb, you should then download the Access 97 upsizing wizard from the Access Developer's web site:

http://www.microsoft.com/accessdev/AccInfo/AUT97dat.htm

Use the Upsizing wizard to upsize the Northwind database to SQL Server or any other ODBC Level 2 compliant back-end database.

The Northwind SQL Order application also demonstrates persistence of data in normalized tables. A MAPI folder allows storage of data in its folder's properties and custom user properties. However, a MAPI folder does not lend itself to normalized storage of data. In the Northwind SQL Order example, a user can create an order with custom properties such as date needed, order id, and order total amount. These fields are stored in the Northwind SQL folder. How can you store data that participates in a one-to-many relationship with the Order ID? In order to create relationships with MAPI folder data, you need to use a database container. If a user creates a Purchase Order and posts that order to the Northwind SQL database, the next time they open the Post item for the order in Outlook, the Northwind SQL Order form 'remembers' the detail items associated with that purchase order's Order ID.

Remember that the Northwind SQL Order application focuses on showing you how to use ODBCDirect in conjunction with Microsoft Forms in an Outlook custom form. Microsoft Forms consist of a special set of lightweight ActiveX controls that are intrinsic to Office 97 applications. This sample application will show you how to populate the Microsoft Forms combo box and list box from the tables in the Northwind SQL database. What you won’t see here is a demonstration of custom actions that might route the purchase order to a supervisor for approval. For additional examples that will expand your knowledge of custom forms design and programming, see the sample applications available on the Outlook web site at http://www.microsoft.com/outlook.

Samples that come with Outlook can only be used with Microsoft Outlook. Users of the Microsoft Exchange Client cannot read Microsoft Outlook forms. For more information on creating forms and public folders, see Building Microsoft Outlook 97 Applications, available from Microsoft Press, and these web sites:

http://www.microsoft.com/outlook/

http://www.microsoft.com/OfficeDev/outlook/

Before running Northwind SQL Sample Application

A. Establish an ODBC DSN for Northwind.

Before you run the Northwind SQL sample application, you must establish a user DSN (Data Source Name) that points to the Nwind database in SQL Server.

To establish a DSN for Nwind:

  1. Click Start menu.

  2. Select Settings | Control Panel option.

  3. Double-click the 32-bit ODBC icon in the control panel.

  4. Click the User DSN tab.

  5. Click the Add button.

  6. Select SQL Server in the list of drivers.

  7. Click the Finish button.

  8. In the Data Source Name edit box, type

    NwindSQL

  9. In the Description edit box, type

    Northwind Sample Database

  10. Click the Options button and enter Nwind as the Database name in the Login frame. Make sure that the server name drop-down is set correctly for your SQL Server. If you have any questions, consult your database or system administrator before you proceed.

  11. Click OK button to accept your addition.

B. Upsize the Access 97 Northwind database.

  1. Use the SQL Enterprise manager to create two database devices for Northwind. Name the first database device Nwind and size this device at 10MB. Name the second device NwindLog and size the device at 2MB.

  2. Create a new database named Nwind. Place the Nwind log on the NwindLog device. Select Manage | Databases and double-click Nwind. Click the Options tab and check the Select Into / Bulk Copy option and the Truncate Log on Checkpoint option.

  3. Open northwind.mdb.

  4. Select Tools | Add-Ins | Upsize to SQL-Server and select Use Existing Database option in the first dialog of the Upsizing wizard. Click Next button.

  5. Double-click NwindSQL in the Select Data Source dialog.

  6. Accept defaults in wizard dialogs and click Finish to upsize the database.

  7. If you do not have access under the SA Login and blank password, you will have to change the connect string in the Northwind SQL example. The connect string assumes a user account of SA and a blank password. Also if you do not login under SA, you will have to grant permissions on table objects in Nwind.

C. Install VBScript version 2.0 and the Microsoft Script Debugger for Internet Explorer.

The VBScript code in this sample application utilizes functions (such as FormatCurrency) available in VBScript version 2.0. If you have not already upgraded your version of VBScript, connect to http://www.microsoft.com/vbscript in order to download VBScript version 2.0. If you are visiting the VBScript forum, you should download the current version of Microsoft Script Debugger for Internet Explorer. At the time of this writing, the Script Debugger was still a beta product. Script Debugger provides a vastly improved development environment for VBScript code in Outlook forms. The script debugger in combination with VBScript 2.0 allows you to set breakpoints in code, step the code using the familiar F8 key, and examine variables in the debugger's immediate window.

D. Connect to the Enterprise Data personal folder.

By default, the information store that contains the Northwind SQL Sample Application will be installed to:

c:\program files\microsoft office\office

Follow these instructions to add the folder to your Outlook folders:

Select File | Open Special Folder | Personal Folder command and open Enterprise Data.pst. See the default installation directory information above. Click OK.

Installation Guidelines

Where should I install the sample application?

If you only want to see how the forms are designed, or if you want to customize the forms that come with the sample application, install the sample for your own private use.

If you want to make the forms available to your organization, or if you want to test the folders, install them in the organization. If you are testing the folder and forms, it is a good idea to restrict access to the public folders while you test them. You must have owner permissions on the organization forms library to install the forms in an organization. For more information, see your Microsoft Exchangeserver administrator.

Install the Northwind SQL Order application for your private use.

Follow this procedure to register the Northwind SQL Order sample form in the Personal Forms Library for your private use.

  1. On the Tools menu, click Options, and then click the Manage Forms tab.

  2. Click Manage Forms button.

  3. Above the box on the left, click Set.

  4. Click Folder Forms Library, click the Northwind SQL folder (in Enterprise Data), and then click OK.

  5. Above the box on the right, click Set.

  6. Click Forms Library, click Personal Forms in the box below, and then click OK.

  7. In the box on the left, click the Northwind SQL Order form.

  8. Click Copy.

  9. Click Close, and then click OK.

Install the Northwind SQL Order application in an organization.

Follow this procedure to register the Northwind SQL Order sample form in the Organization Forms Library for use by yourself and others. You must have administrator privileges to complete this procedure.

  1. On the Tools menu, click Options, and then click the Manage Forms tab.

  2. Click Manage Forms.

  3. Above the box on the left, click Set.

  4. Click Folder Forms Library, click the Northwind SQL folder (in Enterprise Data), and then click OK.

  5. Above the box on the right, click Set.

  6. Click Forms Library, click Organization Forms in the box, and then click OK.

  7. In the box on the left, click the Northwind SQL Order form.

  8. Click Copy.

  9. Click Close, and then click OK.

Using the Northwind SQL Order Form

Examining the VBScript Code Example

To examine the extensive VBScript code that comes with Northwind Order application, post a New Northwind order into the folder. Select the Tools | Design Outlook Form command. Select the Forms | View Code command to view VBScript code. If you wish to examine the code in a different editor than the Outlook Script editor, do the following:

  1. Obtain the Microsoft Internet Explorer Script Debugger and VBScript version 2.0. (See above).

  2. Use Compose | New Northwind SQL Order to create a new item.

  3. Use Tools | Design Outlook Form to switch to design mode. Open the Outlook Script editor by clicking the toolbar icon.

  4. Uncomment the Stop statement at the beginning of the Item_Open VBScript code and then select Script | Run command in the native Outlook Script editor.

  5. If you have installed the Microsoft Internet Explorer Script Debugger, you will see your VBScript code in the improved environment of the Script Debugger. You can set breakpoints, examine variables in the immediate window and the call stack in the call window, and step your code. For Visual Basic and Visual Basic for Applications developers, this editor has a superior feature set compared to the Script Editor that ships with Outlook.

  6. To single step the code in Northwind SQL sample application, press F8 once the Script Debugger editor has entered break mode and is visible.

  7. Check the value of variables by selecting the View | Immediate Window command.

  8. Check the call stack by selecting the View | Call Stack command.

Features of Northwind SQL Sample Application VBScript code

Establishing an ODBCDirect Connection

Using the CreateObject method

Declaring variables and writing code in VBScript

Working with ActiveX controls on an Outlook form

Binding ActiveX controls to a MAPI field

Table 1 - Field Binding for Outlook custom forms

Populating Outlook ActiveX controls with data from an ODBC data source

Using the Column property of ListBox and ComboBox controls

Variant = object.Column( column, row )

In-Line Error Trapping and Transaction Wrappers

VBScript Code

'******************************************************************************
'*                           Northwind SQL Sample Application
'* Purpose: 	Demonstrates use of ODBCDirect in Outlook Forms
'* 			using VBScript and ActiveX controls.
'* Author:  	Randy Byrne, MCSD Charter Member, MVP Outlook 97
'*          	Micro Eye, Microsoft Solution Provider
'*			E-mail: randy_byrne@msn.com or microeye@ncal.net
'* Comments:	If you want to step execution in this code,
'*			remove the comment from the stop statement below.
'*			This code uses functions only available in VBScript 2.0.
'*			You must install VBScript 2.0 to run this script.
'*			You must be using Internet Explorer Script Debugger 1.0
'*			and VBScript 2.0 in order to step code (Press F8).
'*			Both are available on http://www.microsoft.com/vbscript
'*			Extensive VBScript documentation is also available.
'******************************************************************************
'Force explicit variable declaration
Option Explicit
'Remove comment from stop only if you want to step execution of code
'Stop
'Script level declarations
'Remember that in VBScript the AS keyword and typed variables
'are not allowed. All variables are typed as variant by default.
Dim dbe 
Dim wrkODBC
Dim conDB 
Dim Rs
Dim gstrAppName
Dim IsLoading
Dim MyArray
Dim MyArrayShip
Dim MyArrayPO
'Dim page objects and controls collections
Dim objPage
Dim objControls
Dim objPagePO
Dim objControlsPO
'Dim all controls on the form
Dim cmbSuppliers
Dim cmbShippers
Dim lstItems
Dim lstPO
Dim cmdAdd
Dim cmdEditQty
Dim cmdPostPO
Dim cmdDeleteAll
Dim txtOrderID
Dim txtTotal
Dim txtDateNeeded
'Application Name
gstrAppName = "Northwind SQL Sample Application"
'******************************************************************************
'DAO Constants
'Remember that DAO constants must be declared with Const statement.
'The Const statement is only available in VBScript 2.0 or above.
'They cannot be called directly as you would in VB or VBA.
'******************************************************************************
'WorkspaceTypeEnum
Const dbUseODBC=1
'DriverPromptEnum
Const dbDriverComplete = 0
Const dbDriverNoPrompt = 1
Const dbDriverPrompt = 2
Const dbDriverCompleteRequired = 3
'RecordsetTypeEnum
Const dbOpenTable = 1
Const dbOpenDynaset = 2
Const dbOpenSnapshot = 4
Const dbOpenForwardOnly = 8
Const dbOpenDynamic = 16
'RecordsetOptionEnum
Const dbAppendOnly = 8
Const dbConsistent = 32
Const dbDenyRead = 2
Const dbDenyWrite = 1
Const dbExecDirect = 2048
Const dbFailOnError = 128
Const dbForwardOnly = 256
Const dbInconsistent = 16
Const dbReadOnly = 4
Const dbRunAsync = 1024
Const dbSeeChanges = 512
Const dbSQLPassThrough = 64
'******************************************************************************
'Message Box Constants
'Message Box constants do not have to be declared as constants
'They are built into VBScript 2.0
'******************************************************************************
	
'******************************************************************************
'* Procedure:  	Item_Open()
'* Description: 	Upon open, create an ODBCDirect connection to Northwind DSN
'*			If an order number exists in the MAPI Folder, then supply
'*			order details to lstPO and give focus to Purchase Order tab.
'*			If order number is zero, we have a new order and give the
'*			focus to Select Items tab.		
'******************************************************************************
Sub Item_Open()
'Dim procedure variables.
Dim curExtension
Dim lngOrderID
Dim i
Dim strSQL
'Error trapping-remove this statement if you are debugging
'On Error Resume Next
'Set this variable to True to indicate loading state
IsLoading = True
'Set references to pages and controls so that their properties can be set
'Note that controls are set in the Open procedure. You do not have
'to set these controls again in subsequent procedures since their
'variable names have been dimensioned globally.
'Must unhide in case it's hidden otherwise can't set controls
Item.GetInspector.ShowFormPage ("Select Items")
Set objPage = Item.GetInspector.ModifiedFormPages("Select Items")
Set objControls = objPage.Controls
Set cmbSuppliers = objControls("cmbSuppliers")
Set lstItems = objControls("lstItems")
Set cmdAdd = objControls("cmdAdd")
Set cmdEditQty = objControls("cmdEditQty")
Set objPagePO = Item.GetInspector.ModifiedFormPages("Purchase Order")
Set objControlsPO = objPagePO.Controls
Set cmbShippers = objControlsPO("cmbShippers")
Set lstPO = objControlsPO("lstPO")
Set txtOrderID = objControlsPO("txtOrderID")
Set cmdPostPO = objControlsPO("cmdPostPO")
Set cmdDeleteAll = objControlsPO("cmdDeleteAll")
Set txtTotal = objControlsPO("txtTotal")
Set txtDateNeeded = objControlsPO("txtDateNeeded")
'Use the GetODBCConnection function to establish an ODBC Connection
If Not (GetODBCConnection("NWindSQL","ODBC;DSN=NwindSQL;UID=sa;PWD=", _
	dbDriverCompleteRequired)) Then
	Exit Sub
End If
'Hide the message page
Item.GetInspector.HideFormPage ("Message")
'If the order number exists (<>0), then fill lstPO (persistent data on form)
'Otherwise, we have a new order and give Select Items tab the focus
lngOrderID = Item.UserProperties("Order Number")
If lngOrderID = 0 Then
	'New order
	Item.GetInspector.ShowFormPage ("Select Items")
	Item.GetInspector.SetCurrentFormPage ("Select Items")
	txtTotal.Text = FormatCurrency(0)
	lstPO.Clear
	cmdPostPO.Enabled = True
	cmdDeleteAll.Enabled = True
	cmdAdd.Enabled = True
Else
	'Existing Order
	Item.GetInspector.HideFormPage ("Select Items")
	Item.GetInspector.SetCurrentFormPage ("Purchase Order")
	lstPO.Clear
	'Retrieve persistent data from Database
	strSQL = "SELECT Order_Details.ProductID, Products.ProductName,"
 	strSQL = strSQL & "Order_Details.Quantity, Products.QuantityPerUnit,"
	strSQL = strSQL & "Order_Details.UnitPrice FROM Products"
	strSQL = strSQL & " INNER JOIN Order_Details ON"
	strSQL = strSQL & " Products.ProductID = Order_Details.ProductID"
	strSQL = strSQL & " Where Order_Details.OrderID = " & lngOrderID
	Set RS = conDB.OpenRecordset(strSQL, dbOpenSnapshot)
	If RS.RecordCount Then
		i = 0
		Redim MyArrayPO(RS.RecordCount - 1, 5)
		Do Until RS.EOF
			MyArrayPO(i, 0) = RS(0)
			MyArrayPO(i, 1) = RS(1)
			MyArrayPO(i, 2) = RS(2)
			MyArrayPO(i, 3) = RS(3)
			MyArrayPO(i, 4) = FormatCurrency(RS(4), 2)
			curExtension = RS(2)* RS(4)
			MyArrayPO(i, 5) = FormatCurrency(curExtension, 2)
			i = i + 1
			RS.MoveNext
		Loop
		lstPO.List = MyArrayPO
	End IF
	cmdPostPO.Enabled = False
	cmdDeleteAll.Enabled = False
	cmdAdd.Enabled = False
End If
'Create a recordset to fill an array
'Caution: VBScript does not support the use of bang (!) operator
'to reference fields within recordset such as RS!CompanyName
strSQL = "Select CompanyName, Phone, SupplierId "
strSQL = strSQL & "from Suppliers ORDER BY CompanyName"
Set RS = conDB.OpenRecordset(strSQL, dbOpenSnapshot)
If RS.RecordCount Then
    i = 0
    ReDim MyArray(RS.RecordCount-1, RS.Fields.Count)
    Do Until RS.EOF
	  MyArray(i, 0) = RS(0)
	  MyArray(i, 1) = RS(1)
	  MyArray(i, 2) = RS(2)
	  i = i + 1	
       RS.MoveNext
    Loop
    RS.Close
    'Use the List method to populate the cmbSuppliers drop-down
    cmbSuppliers.List = MyArray
    cmbSuppliers.ListIndex = 0
End If
strSQL = "Select companyname, phone, shipperID "
strSQL = strSQL + "from shippers order by companyname"
Set RS = conDB.OpenRecordset(strSQL, dbOpenSnapshot)
If RS.RecordCount Then
    ReDim MyArrayShip(RS.RecordCount, RS.Fields.Count)
    MyArrayShip = RS.GetRows(RS.RecordCount)
    RS.Close
    'Use the Column method to populate the cmbShippers drop-down
    cmbShippers.Column = MyArrayShip
End If
'Create dynamic SQL for items recordset
strSQL = "SELECT ProductName, QuantityperUnit, UnitPrice, ProductID"
strSQL = strSQL & " FROM products"
strSQL = strSQL & " WHERE Discontinued = 0 and SupplierID = "
strSQL = strSQL & cmbSuppliers.Column(2)
strSQL = strSQL & " ORDER BY ProductName"
Set RS = conDB.OpenRecordset(strSQL, dbOpenSnapshot)
If RS.RecordCount Then
   	ReDim MyArray(RS.RecordCount - 1, 4)
	i = 0
    	lstItems.Clear
    	Do Until RS.EOF
        	'Populate lstItems
	  	MyArray(i, 0) = RS(0)
		MyArray(i, 1) = 1
		MyArray(i, 2) = RS(1)
		MyArray(i, 3) = FormatCurrency(RS(2), 2)
		MyArray(i, 4) = RS(3)
		lstItems.List = MyArray
		i = i + 1
        	RS.MoveNext
    	Loop
      RS.Close    
End If
'Now the cmbSuppliers control can respond to a change event
Isloading = False
End Sub
'******************************************************************************
'* Procedure:  	Item_Write()
'* Description: 	Note: This procedure only works if it is defined
'*			as a function rather than a sub procedure.
'*			Illustrates the use of Outlook events to enforce
'*			business rules.
'******************************************************************************
Function Item_Write()
On Error Resume Next
Dim lngOrderID
'Access Order Number through the UserProperties collection
lngOrderID = Item.UserProperties("Order Number")
If lngOrderID = 0 and lstPO.ListCount > 0 Then
	If MsgBox("You are posting an open order to the Northwind folder." _
	& Chr(13) & "Use the Post Purchase Order command" & Chr(13) _
	& "to post to the database and folder." & Chr(13) _
	& "Continue to post open order?", _
	vbQuestion + vbYesNo , gstrAppName) = vbYes Then
		Item_Write = True
	Else
		Item_Write = False
	End If
End If
End Function
'******************************************************************************
'* Procedure:  	Item_Close()
'* Description: 	Housekeeping function to close ODBC connection.
'******************************************************************************
Function Item_Close()
On Error Resume Next
conDB.Close
wrkODBC.Close
End Function
'******************************************************************************
'* Procedure:  	Item_CustomPropertyChange(ByVal MyPropName)
'* Description: 	You use a custom property change event to respond to a change
'*			in supplier in drop-down. You can't use the control's 
'*			intrinsic events. These events currently do not fire
'*			in the VBScript of an Outlook form.
'******************************************************************************
Sub Item_CustomPropertyChange(ByVal MyPropName)
Dim strSQL
Dim i
Select Case MyPropName
Case "Supplier"
	If Not(IsLoading) Then
    	lstItems.Clear
    	strSQL = "SELECT ProductName, QuantityperUnit, UnitPrice, ProductID"
	strSQL = strSQL & " FROM products"
	strSQL = strSQL & " WHERE Discontinued = 0 and SupplierID = "
	strSQL = strSQL & cmbSuppliers.Column(2)
	strSQL = strSQL & " ORDER BY ProductName;"
	Set RS = conDB.OpenRecordset(strSQL, dbOpenSnapshot)
    	If RS.RecordCount Then
		ReDim MyArray(RS.RecordCount - 1, 4)
		i = 0
    		lstItems.Clear
       	Do Until RS.EOF
        		'Populate lstItems
	  		MyArray(i, 0) = RS(0) 'Produce Name Column 0
			MyArray(i, 1) = 1     'Initial Qty  Column 1
			MyArray(i, 2) = RS(1) 'Unit per Qty Column 2
			MyArray(i, 3) = FormatCurrency(RS(2), 2) 'Unit Price Column 3
			MyArray(i, 4) = RS(3) 'Item ID Column 4
			lstItems.List = MyArray
			i = i + 1
        		RS.MoveNext
    	  	Loop
    	  	RS.Close
    	End If
	End If
End Select
End Sub
'******************************************************************************
'* Procedure:  	cmdAdd_Click()
'* Description: 	This procedure adds selected items from lstItems
'*			to the lstPO. Copies the existing elements of MyArrayPO
'*			into TempArrayPO and adds selected items in lstItems
'*			to TempArrayPO. Finally uses the List method to 
'*			repopulate lstPO with old and new selected items.
'******************************************************************************
Sub cmdAdd_Click()
Dim i
Dim j
Dim intUbound
Dim curTotal
Dim curExtension
Dim intSelected
Dim TempArrayPO 
curTotal = CCur(txtTotal.Text)
intSelected = 0
'Determine the count of select items in lstItems
For i = 0 To lstItems.ListCount - 1
	If lstItems.Selected(i) Then
		intSelected = intSelected + 1
	End if
Next
'Bail out if none are selected
If intSelected = 0 Then
	MsgBox "You must select items in the list.", vbInformation, gstrAppName
	Exit Sub
End If
'If lstPO contains items then copy MyArray into TempArrayPO
If lstPO.ListCount Then
	intUbound = lstPO.ListCount + (intSelected - 1)
	Redim TempArrayPO(intUbound, 5)
	For i = 0 to Ubound(MyArrayPO, 1)
		For j = 0 to Ubound(MyArrayPO, 2)
			TempArrayPO(i, j) = MyArrayPO(i, j)
		Next
	Next
	j = lstPO.ListCount
	For i = 0 to lstItems.ListCount -1
		If lstItems.Selected(i) Then
			TempArrayPO(j, 0) = lstItems.Column(4, i)
			TempArrayPO(j, 1) = lstItems.Column(0, i)
			TempArrayPO(j, 2) = lstItems.Column(1, i)
			TempArrayPO(j, 3) = lstItems.Column(2, i)
			TempArrayPO(j, 4) = lstItems.Column(3, i)
			curExtension = lstItems.Column(1, i) * lstItems.Column(3, i)
			TempArrayPO(j, 5) = FormatCurrency(curExtension, 2)
			curTotal = curTotal + curExtension
			j = j + 1
		End If
	Next
	Redim MyArrayPO(intUbound, 5)
	MyArrayPO = TempArrayPO
	lstPO.List = MyArrayPO
Else
	Redim MyArrayPO(intSelected - 1, 5)
	j = 0
	For i = 0 to lstItems.ListCount -1
		If lstItems.Selected(i) Then
			MyArrayPO(j, 0) = lstItems.Column(4, i)
			MyArrayPO(j, 1) = lstItems.Column(0, i)
			MyArrayPO(j, 2) = lstItems.Column(1, i)
			MyArrayPO(j, 3) = lstItems.Column(2, i)
			MyArrayPO(j, 4) = lstItems.Column(3, i)
			curExtension = lstItems.Column(1, i) * lstItems.Column(3, i)
			MyArrayPO(j, 5) = FormatCurrency(curExtension, 2)
			curTotal = curTotal + curExtension
			j = j + 1
		End If
	Next
	lstPO.List = MyArrayPO
End If
'Set total in txtTotal
txtTotal.Text = FormatCurrency(curTotal)	
End Sub
'******************************************************************************
'* Procedure:  	cmdDeleteAll_Click()
'* Description: 	Enables deletion of all items in lstPO using clear method.
'******************************************************************************
Sub cmdDeleteAll_Click()
'Use the clear method to remove all items from list box
lstPO.Clear
'Reset total to zero
txtTotal.Text = FormatCurrency(0, 2)
End Sub
'******************************************************************************
'* Procedure:  	cmdAbout_Click()
'* Description: 	Show About Message Box.
'******************************************************************************
Sub cmdAbout_Click()
MsgBox "Northwind SQL Sample Application code by:" & chr(13) _
& "Randy Byrne, MCSD, Micro Eye" & chr(13) & "E-mail: randy_byrne@msn.com" _
& chr(13) & chr(13) & "Uses Northwind SQL Sample Database" _
& chr(13) & "to demonstrate ODBCDirect in Outlook Forms." _
& chr(13) & "You must upsize Northwind database and" _
& chr(13) & "establish NwindSQL DSN before you open this form.", _
vbInformation, "About Northwind SQL Sample Application"
End Sub
'******************************************************************************
'* Procedure:  	cmdPostPO_Click()
'* Description: 	Uses insert into to insert PO items into Northwind.
'******************************************************************************
Sub cmdPostPO_Click()
Dim i
Dim strSQL
Dim lngOrderID
Dim intFail 
'Use in-line error trapping
On Error Resume Next
IntFail = 0
If lstPO.ListCount = 0 Then
	MsgBox "Add items to Purchase Order before posting!", _
	vbExclamation, gstrAppName
	Exit Sub
End If
'Begin transaction wrapper
'Note-This transaction would ideally be wrapped in an ActiveX Automation server
wrkODBC.BeginTrans
'Insert into orders table
strSQL = "Insert Into Orders (CustomerID,EmployeeID,ShipVia,OrderDate,RequiredDate)" _
& " Values ('WHITC',1," & cmbShippers.Column(2) & ",'" & Now() _
& "','" & txtDateNeeded.Text & "')"
conDB.Execute strSQL
If Err <> 0 Then
	MsgBox Err.Description , vbCritical, gstrAppName
	intFail = True
	Err.Clear
End If
'Returns OrderId of order just added
strSQL = "Select Max(OrderID) from Orders"
Set RS = conDB.OpenRecordset(strSQL)
lngOrderID = RS(0)
'Insert line items in lstPO into order detail table
For i = 0 To lstPO.ListCount - 1
	strSQL = "Insert Into Order_Details (OrderID, ProductID, UnitPrice,"
	strSQL = strSQL & " Quantity, Discount) Values (" & lngOrderID
	strSQL = strSQL & "," & lstPO.Column(0, i)
	strSQL = strSQL & "," & cDbl(lstPO.Column(4, i))
	strSQL = strSQL & "," & cDbl(lstPO.Column(2, i))
	strSQL = strSQL & ",0)"
	conDB.Execute strSQL
	If Err <> 0 Then
		MsgBox Err.Description , vbCritical, gstrAppName
		intFail = True
		Err.Clear
	End If
Next
'Commit the transaction if no error occurred
If Not (intFail) Then
	wrkODBC.CommitTrans
	txtOrderID.Text = lngOrderID
	cmdPostPO.Enabled = False
	cmdDeleteAll.Enabled = False
	cmdAdd.Enabled = False
	Item.GetInspector.HideFormPage ("Select Items")
	Item.Subject = "Northwind Order " & lngOrderID
	'Save the form item.
	Item.Save
Else
	MsgBox "Could not post the order!", vbCritical, gstrAppName
	wrkODBC.Rollback
End If
End Sub
'******************************************************************************
'* Procedure:  	cmdEditQty_Click()
'* Description: 	Since the grid cannot be edited directly, this allows
'*			the user to change the quantity for a selected items.
'******************************************************************************
Sub cmdEditQty_Click()
Dim i
Dim intQty
intQty=InputBox ("Enter Quantity", gstrAppName)
If Cint(intQty)> 0 Then
	For i = 0 to lstItems.ListCount - 1
		If lstItems.Selected(i) Then
			lstItems.Column(1, i) = intQty
		End If
	Next
End If
End Sub
'******************************************************************************
'* Procedure:  	GetODBCConnection (ByVal MyDSN, ByVal MyConn, ByVal MyPrompt)
'* Description: 	This procedure establishes an ODBCDirect connection object
'*			which can be used to open recordsets and execute SQL code.
'* Arguments:	MyDSN = A valid User DSN.
'*			MyConn = A valid ODBC connect property string.
'*			Example: "ODBC;DSN=Nwind" or "ODBC;DSN=Pubs"			
'*			MyPrompt = A valid option value specifying ODBC driver
'*			prompt options.
'*			Note: The options argument determines if and when to prompt
'*			the user to establish the connection.
'* Returns:		TRUE if successful; FALSE if connection fails.
'******************************************************************************
Function GetODBCConnection (ByVal MyDSN, ByVal MyConn, ByVal MyPrompt)
Dim strUser
Dim strPass
'Turn on error trappping
On Error Resume Next
'Set to defaults-change these values if required
strUser = "admin"
strPass = ""
'Create a DAO object. You must use DAO.dbEngine.35 or you will cause
'a page fault on machines with both DAO 3 and DAO 3.5 installed.
Set dbe = Item.Application.CreateObject("DAO.dbEngine.35")
If Err.Number <> 0 Then
    	Msgbox "Error#: " & err.number & chr(13) & err.description & chr(13) _
 	& "Warning -- Could not create DAO 3.5 Object!" & Chr(13) _
	& "Please make sure that DAO 3.5 is installed on this machine!", _
	vbCritical, gstrAppName
	GetODBCConnection = False
    	Exit Function
End If
'Create an ODBCDirect Workspace
Set wrkODBC = dbe.CreateWorkspace("ODBCWorkspace", strUser , strPass , dbUseODBC)
If Err.Number <> 0 Then
    	Msgbox "Error#: " & err.number & chr(13) & err.description & chr(13) _
	& "Warning -- Could not create ODBC workspace!" & chr(13) _
	& "Please make sure that user name and password are correct.", _
	vbCritical, gstrAppName
	GetODBCConnection = False
    	Exit Function
End If
dbe.Workspaces.Append wrkODBC
'Establish the connection to DSN
Set conDB = wrkODBC.OpenConnection("Connection1", MyPrompt, , MyConn)
If Err.Number <> 0 Then
    	Msgbox "Error#: " & err.number & chr(13) & err.description & chr(13) _
	& "Warning -- Could not create connection to " & MyDSN & "!" & chr(13) _
	& "Please make sure that " & MyDSN & " is a valid DSN!", _
	vbCritical, gstrAppName
	GetODBCConnection = False
    	Exit Function
End If
GetODBCConnection = True
End Function

Visual Basic 5.0 Code for Outlook List Server

The following code is for ListServerRDO.vbp is contained in one module and one class. You must use Tools | References to set a reference to Microsoft Remote Data Objects 2.0. This listing contains the complete code for ListServerRDO:

'******************************************************************************
'Name: basListServer
'Purpose: Module level code for ListServerRDO
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/20/97
'Arguments: None
'Returns: None
'*******************************************************************************
Option Explicit
Public lngInstanceCount As Integer
Public intColumnCount As Integer
Public strConnect As String
Public blnConnected As Boolean
Public MyEnv As rdoEnvironment
Public MyConn As rdoConnection
Public avarSuppliers As Variant
Public avarShippers As Variant
Public Const ERR_CONNECT = 1000
Public Const ERR_RECORDSET = 1001
Declare Function GetTickCount Lib "Kernel32" () As Long
'Entry point of component
Sub Main()
End Sub
'******************************************************************************
'Name: RDOOpen
'Purpose: Open rdoConnection
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/20/97
'Arguments: None
'Returns: True if successful, False if error
'*******************************************************************************
Function RDOOpen() As Boolean
Dim lngRowCount As Long
Dim MyRSSuppliers As rdoResultset
Dim MyRSShippers As rdoResultset
Dim strSQL As String
On Error Resume Next
Set MyEnv = rdoEngine.rdoEnvironments(0)
Set MyConn = MyEnv.OpenConnection("", _
rdDriverNoPrompt, , strConnect)
If Err <> 0 Then
    RDOOpen = False
    blnConnected = False
    On Error GoTo 0
    Err.Raise vbObjectError + ERR_CONNECT, , _
    "Connection to database failed!" _
    & vbCr & "Connect String:" & vbCr & strConnect
    Exit Function
Else
    RDOOpen = True
    blnConnected = True
End If
On Error Resume Next
'Create a static variant array that is passed when GetSuppliers
'method is called from base client
strSQL = "Select CompanyName, Phone, SupplierId "
strSQL = strSQL & "from Suppliers ORDER BY CompanyName"
Set MyRSSuppliers = MyConn.OpenResultset(strSQL, rdOpenStatic)
lngRowCount = MyRSSuppliers.RowCount
If lngRowCount > 0 Then
    ReDim avarSuppliers(lngRowCount, MyRSSuppliers.rdoColumns.Count)
    avarSuppliers = MyRSSuppliers.GetRows(lngRowCount)
End If
strSQL = "Select companyname, phone, shipperID "
strSQL = strSQL + "from shippers order by companyname"
Set MyRSShippers = MyConn.OpenResultset(strSQL, rdOpenStatic)
lngRowCount = MyRSShippers.RowCount
If lngRowCount > 0 Then
    ReDim avarShippers(lngRowCount, MyRSShippers.rdoColumns.Count)
    avarShippers = MyRSShippers.GetRows(lngRowCount)
End If
MyRSShippers.Close
MyRSSuppliers.Close
End Function
'******************************************************************************
'Name: RDOClose
'Purpose: Close rdoConnection
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/20/97
'Arguments: None
'Returns: None
'*******************************************************************************
Sub RDOClose()
On Error Resume Next
MyEnv.Close
MyConn.Close
blnConnected = False
strConnect = ""
End Sub
Option Explicit
'******************************************************************************
'Name: ClistServer Class
'Purpose: Provide Variant Arrays to controls on Outlook Forms
'Uses: Remote Data Objects 2.0
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/21/97
'*******************************************************************************
Private Sub Class_Initialize()
lngInstanceCount = lngInstanceCount + 1
End Sub
Private Sub Class_Terminate()
lngInstanceCount = lngInstanceCount - 1
If lngInstanceCount = 0 Then
    'Close the rdoConnection
    RDOClose
End If
End Sub
'******************************************************************************
'Name: GetList
'Purpose: Returns variant array to base client
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/20/97
'Arguments: strSQL
'Returns: Variant Array populated with resultset created by strSQL
'*******************************************************************************
Public Function GetList(ByVal strSQL As String) As Variant
Dim RS As rdoResultset
Dim avarList As Variant
Dim intRows As Integer
Dim intColumns As Integer
On Error Resume Next
If MyConn Is Nothing Then
    ReDim avarList(0, 0)
    GetList = avarList
    On Error GoTo 0
    Err.Raise vbObjectError + ERR_CONNECT, , _
    "Connection to database failed!" _
    & vbCr & "Connect String:" & vbCr & strConnect
Else
    Set RS = MyConn.OpenResultset(strSQL, rdOpenStatic)
    If Err <> 0 Then
        On Error GoTo 0
        Err.Raise vbObjectError + ERR_RECORDSET, , _
        "Could not open Recordset for SQL statement" _
        & vbCr & strSQL
    Else
        intRows = RS.RowCount
        intColumns = RS.rdoColumns.Count
        intColumnCount = intColumns
    End If
    If intRows Then
        ReDim avarList(intRows, intColumns)
        avarList = RS.GetRows(intRows)
        GetList = avarList
    Else
        intColumnCount = 1
        ReDim avarList(0, 0)
        GetList = avarList
    End If
    RS.Close
End If
End Function
'******************************************************************************
'Name: GetTick
'Purpose: Return number of seconds elapsed since midnight to base client
'This function is a wrapper for Win32 GetTickCount
'API calls cannot be made from within VBScript
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/12/97
'Arguments: None
'Returns: None
'*******************************************************************************
Public Function GetTick() As Long
GetTick = GetTickCount
End Function
'******************************************************************************
'Name: GetSuppliers
'Purpose: Returns variant array to base client
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/20/97
'Arguments: None
'Returns: None
'*******************************************************************************
Public Function GetSuppliers() As Variant
If MyConn Is Nothing Then
    ReDim avarSuppliers(0, 0)
    GetSuppliers = avarSuppliers
    Err.Raise vbObjectError + ERR_CONNECT, , _
    "Connection to database failed!" _
    & vbCr & "Connect String:" & vbCr & strConnect
Else
    intColumnCount = UBound(avarSuppliers) + 1
    GetSuppliers = avarSuppliers
End If
End Function
'******************************************************************************
'Name: GetShippers
'Purpose: Returns variant array to base client
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/20/97
'Arguments: None
'Returns: None
'*******************************************************************************
Public Function GetShippers() As Variant
If MyConn Is Nothing Then
    ReDim avarShippers(0, 0)
    GetShippers = avarShippers
    Err.Raise vbObjectError + ERR_CONNECT, , _
    "Connection to database failed!" _
    & vbCr & "Connect String:" & vbCr & strConnect
Else
    intColumnCount = UBound(avarSuppliers) + 1
    GetShippers = avarShippers
    
End If
End Function
'******************************************************************************
'Name: ConnectString Property Let
'Purpose: Establish rdoConnection based on strConn
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/20/97
'Arguments: strConn
'Returns: None
'*******************************************************************************
Public Property Let ConnectString(ByVal strConn As String)
    If strConnect = "" Then
        strConnect = strConn
        RDOOpen
    Else
        If Not (blnConnected) And strConn <> strConnect Then
            'Close existing connection
            RDOClose
            'Open new connection
            RDOOpen
            If blnConnected Then
                strConnect = strConn
            End If
        End If
        If Not (blnConnected) Then
            RDOOpen
        End If
    End If
End Property
'******************************************************************************
'Name: ConnectString Property Get
'Purpose: Return strConnect to base client
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/20/97
'Arguments: None
'Returns: strConnect
'*******************************************************************************
Public Property Get ConnectString() As String
    ConnectString = strConnect
End Property
'******************************************************************************
'Name: ColumnCount Property Get
'Purpose: Returns intColumnCount to base client
'Created By: Randy Byrne, randy_byrne@msn.com
'Date: 3/20/97
'Arguments: None
'Returns: intColumnCount
'*******************************************************************************
Public Property Get ColumnCount() As Integer
    ColumnCount = intColumnCount
End Property

Additional Sources of Information

Available on http://www.microsoft.com:

Building Custom Applications with Microsoft Outlook

Building Outlook Information-Sharing Solutions

Available on http://www.microsoft.com/outlookdev

The Microsoft Outlook 97 Automation Server Programming Model
http://www.microsoft.com/OutlookDev/TechInfo/outprog.htm

Using ODBCDirect in Outlook 97: The Northwind Order Application
http://www.microsoft.com/OutlookDev/TechInfo/odbcoutl.htm

From Office 97/Visual Basic Programmer’s Guide
http://www.microsoft.com/OfficeDev/Docs/OPG/

Understanding Object Models
http://www.microsoft.com/OfficeDev/Docs/Opg/002/002.htm

Microsoft Outlook Objects
http://www.microsoft.com/OfficeDev/Docs/Opg/005/005.htm

Data Access Objects
http://www.microsoft.com/OfficeDev/Docs/Opg/011/011.htm

Accessing Exchange and Outlook Data Using Visual Basic
http://www.microsoft.com/AccessDev/AccWhite/ExcOutlk.htm

Building Successful Client/Server Applications
http://www.microsoft.com/visualtools/scenarios/ClientServer.htm

Web Collaboration and Workflow Applications
http://www.microsoft.com/visualtools/scenarios/WebWorkflow.htm

Integrate the Enterprise
http://www.microsoft.com/visualtools/scenarios/LegacyInteroperability.htm

Data Access Interfaces
http://www.microsoft.com/visualtools/strategy/DataAccess.htm

Other Important Sites for Microsoft Outlook:

Outlook Resource Site
http://outlook.useast.net/outlook/

Slipstick Systems Microsoft ExchangeCenter
http://www.slipstick.com/exchange/

© 1997 Microsoft Corporation. All rights reserved.

The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication.

This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS SUMMARY.

Microsoft and Visual Basic are registered trademarks and SQL Server is a trademark of Microsoft Corporation.

DB2 is a registered trademark of International Business Machines Corporation. Informix is a registered trademark of Informix Softeware, Inc. ORACLE is a registered trademark of Oracle Corporation. Paradox is a registered trademark of Borland Ingernational, Inc.

Other product or company names mentioned herein may be the trademarks of their respective owners.